From 5352e9abb72ba98e787aeda43014506c2fefdf98 Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Thu, 22 Oct 2009 00:53:24 +0000 Subject: [PATCH] DSM exceptions. SEMS-59. actions and conditions should be able to throw exceptions. if an exceptions happens, execution of the current actions is interrupted, and special exception transitions are executed. git-svn-id: http://svn.berlios.de/svnroot/repos/sems/trunk@1554 8eb893ce-cfd4-0310-b710-fb5ebe64c474 --- apps/dsm/DSMChartReader.cpp | 6 + apps/dsm/DSMChartReader.h | 3 +- apps/dsm/DSMCoreModule.cpp | 25 +++ apps/dsm/DSMCoreModule.h | 1 + apps/dsm/DSMStateEngine.cpp | 193 +++++++++++++---------- apps/dsm/DSMStateEngine.h | 22 ++- apps/dsm/doc/Readme.dsm.txt | 27 ++-- apps/dsm/doc/examples/test_exception.dsm | 10 ++ 8 files changed, 193 insertions(+), 94 deletions(-) create mode 100644 apps/dsm/doc/examples/test_exception.dsm diff --git a/apps/dsm/DSMChartReader.cpp b/apps/dsm/DSMChartReader.cpp index a9e94de5..37d1e1cf 100644 --- a/apps/dsm/DSMChartReader.cpp +++ b/apps/dsm/DSMChartReader.cpp @@ -340,6 +340,7 @@ bool DSMChartReader::decode(DSMStateDiagram* e, const string& chart, } tr->precond = cl->conditions; + tr->is_exception = cl->is_exception; delete cl; // start AL_trans action list @@ -354,6 +355,11 @@ bool DSMChartReader::decode(DSMStateDiagram* e, const string& chart, continue; } + if (token == "exception") { + cl->is_exception = true; + continue; + } + // DBG("new condition: '%s'\n", token.c_str()); DSMCondition* c = conditionFromToken(token, cl->invert_next); cl->invert_next = false; diff --git a/apps/dsm/DSMChartReader.h b/apps/dsm/DSMChartReader.h index 377916d7..b2ce4df7 100644 --- a/apps/dsm/DSMChartReader.h +++ b/apps/dsm/DSMChartReader.h @@ -69,9 +69,10 @@ class ActionList : public DSMElement { }; struct DSMConditionList : public DSMElement { - DSMConditionList() : invert_next(false) { } + DSMConditionList() : invert_next(false), is_exception(false) { } vector conditions; bool invert_next; + bool is_exception; }; class DSMChartReader { diff --git a/apps/dsm/DSMCoreModule.cpp b/apps/dsm/DSMCoreModule.cpp index 60cc39fd..fcef7870 100644 --- a/apps/dsm/DSMCoreModule.cpp +++ b/apps/dsm/DSMCoreModule.cpp @@ -44,6 +44,8 @@ DSMAction* DSMCoreModule::getAction(const string& from_str) { DEF_CMD("callFSM", SCCallFSMAction); DEF_CMD("returnFSM", SCReturnFSMAction); + DEF_CMD("throw", SCThrowAction); + DEF_CMD("stop", SCStopAction); DEF_CMD("playPrompt", SCPlayPromptAction); @@ -257,6 +259,29 @@ EXEC_ACTION_START(SCDisableDTMFDetection) { sess->setDtmfDetectionEnabled(false); } EXEC_ACTION_END; +CONST_ACTION_2P(SCThrowAction, ',', true); +EXEC_ACTION_START(SCThrowAction) { + map e_args; + e_args["type"] = resolveVars(par1, sess, sc_sess, event_params); + DBG("throwing DSMException type '%s'\n", e_args["type"].c_str()); + + string e_params = resolveVars(par2, sess, sc_sess, event_params); + + // inefficient param-split + vector params = explode(e_params, ";"); + for (vector::iterator it= + params.begin(); it != params.end(); it++) { + vector n = explode(*it, "="); + if (n.size()==2) { + e_args[n[0]]=n[1]; + } + } + + throw DSMException(e_args); + +} EXEC_ACTION_END; + + EXEC_ACTION_START(SCStopAction) { if (resolveVars(arg, sess, sc_sess, event_params) == "true") { DBG("sending bye\n"); diff --git a/apps/dsm/DSMCoreModule.h b/apps/dsm/DSMCoreModule.h index 63b1984e..7ad747c5 100644 --- a/apps/dsm/DSMCoreModule.h +++ b/apps/dsm/DSMCoreModule.h @@ -70,6 +70,7 @@ DEF_SCModSEStrArgAction(SCJumpFSMAction); DEF_SCModSEStrArgAction(SCCallFSMAction); DEF_SCModSEStrArgAction(SCReturnFSMAction); +DEF_ACTION_2P(SCThrowAction); DEF_ACTION_2P(SCSetAction); DEF_ACTION_2P(SCAppendAction); diff --git a/apps/dsm/DSMStateEngine.cpp b/apps/dsm/DSMStateEngine.cpp index 250379a4..31d87896 100644 --- a/apps/dsm/DSMStateEngine.cpp +++ b/apps/dsm/DSMStateEngine.cpp @@ -156,9 +156,9 @@ bool DSMStateEngine::onInvite(const AmSipRequest& req, DSMSession* sess) { } bool DSMStateEngine::runactions(vector::iterator from, - vector::iterator to, - AmSession* sess, DSMCondition::EventType event, - map* event_params, bool& is_consumed) { + vector::iterator to, + AmSession* sess, DSMCondition::EventType event, + map* event_params, bool& is_consumed) { // DBG("running %zd actions\n", to - from); for (vector::iterator it=from; it != to; it++) { DBG("executing '%s'\n", (*it)->name.c_str()); @@ -205,11 +205,19 @@ void DSMStateEngine::addModules(vector modules) { bool DSMStateEngine::init(AmSession* sess, const string& startDiagram, DSMCondition::EventType init_event) { - if (!jumpDiag(startDiagram, sess, init_event, NULL)) { - ERROR("initializing with start diag '%s'\n", - startDiagram.c_str()); - return false; - } + try { + if (!jumpDiag(startDiagram, sess, init_event, NULL)) { + ERROR("initializing with start diag '%s'\n", + startDiagram.c_str()); + return false; + } + } catch (DSMException& e) { + DBG("Exception type '%s' occured while initializing! Run init event as exception...\n", + e.params["type"].c_str()); + runEvent(sess, init_event, &e.params, true); + return true; + + } DBG("run init event...\n"); runEvent(sess, init_event, NULL); @@ -245,98 +253,119 @@ bool DSMCondition::match(AmSession* sess, } void DSMStateEngine::runEvent(AmSession* sess, - DSMCondition::EventType event, - map* event_params) { + DSMCondition::EventType event, + map* event_params, + bool run_exception) { if (!current || !current_diag) return; + + DSMCondition::EventType active_event = event; + map* active_params = event_params; + map exception_params; + bool is_exception = run_exception; bool is_consumed = true; do { - is_consumed = true; + try { + is_consumed = true; - for (vector::iterator tr = current->transitions.begin(); - tr != current->transitions.end();tr++) { - DBG("checking transition '%s'\n", tr->name.c_str()); - - vector::iterator con=tr->precond.begin(); - while (con!=tr->precond.end()) { - if (!(*con)->_match(sess, event, event_params)) - break; - con++; - } - if (con == tr->precond.end()) { - DBG("transition '%s' matched.\n", tr->name.c_str()); + for (vector::iterator tr = current->transitions.begin(); + tr != current->transitions.end();tr++) { + if (tr->is_exception != is_exception) + continue; - // matched all preconditions - // find target state - State* target_st = current_diag->getState(tr->to_state); - if (!target_st) { - ERROR("script writer error: transition '%s' from " - "state '%s' to unknown state '%s'\n", - tr->name.c_str(), - current->name.c_str(), - tr->to_state.c_str()); - } + DBG("checking transition '%s'\n", tr->name.c_str()); - // run post-actions - if (current->post_actions.size()) { - DBG("running %zd post_actions of state '%s'\n", - current->post_actions.size(), current->name.c_str()); - if (runactions(current->post_actions.begin(), - current->post_actions.end(), - sess, event, event_params, is_consumed)) { + vector::iterator con=tr->precond.begin(); + while (con!=tr->precond.end()) { + if (!(*con)->_match(sess, active_event, active_params)) break; - } + con++; } - - // run transition actions - if (tr->actions.size()) { - DBG("running %zd actions of transition '%s'\n", - tr->actions.size(), tr->name.c_str()); - if (runactions(tr->actions.begin(), - tr->actions.end(), - sess, event, event_params, is_consumed)) { + if (con == tr->precond.end()) { + DBG("transition '%s' matched.\n", tr->name.c_str()); + + // matched all preconditions + // find target state + State* target_st = current_diag->getState(tr->to_state); + if (!target_st) { + ERROR("script writer error: transition '%s' from " + "state '%s' to unknown state '%s'\n", + tr->name.c_str(), + current->name.c_str(), + tr->to_state.c_str()); + } + + // run post-actions + if (current->post_actions.size()) { + DBG("running %zd post_actions of state '%s'\n", + current->post_actions.size(), current->name.c_str()); + if (runactions(current->post_actions.begin(), + current->post_actions.end(), + sess, active_event, active_params, is_consumed)) { + break; + } + } + + // run transition actions + if (tr->actions.size()) { + DBG("running %zd actions of transition '%s'\n", + tr->actions.size(), tr->name.c_str()); + if (runactions(tr->actions.begin(), + tr->actions.end(), + sess, active_event, active_params, is_consumed)) { + break; + } + } + + // go into new state + if (!target_st) { break; } - } - - // go into new state - if (!target_st) { - break; - } - DBG("changing to new state '%s'\n", target_st->name.c_str()); - MONITORING_LOG(sess->getLocalTag().c_str(), "dsm_state", target_st->name.c_str()); - + DBG("changing to new state '%s'\n", target_st->name.c_str()); + #ifdef USE_MONITORING - if (DSMFactory::MonitoringFullTransitions) { - MONITORING_LOG_ADD(sess->getLocalTag().c_str(), - "dsm_stategraph", - ("> "+ tr->name + " >").c_str()); - } - - if (DSMFactory::MonitoringFullCallgraph) { - MONITORING_LOG_ADD(sess->getLocalTag().c_str(), - "dsm_stategraph", - (current_diag->getName() +"/"+ target_st->name).c_str()); - } + MONITORING_LOG(sess->getLocalTag().c_str(), "dsm_state", target_st->name.c_str()); + + if (DSMFactory::MonitoringFullTransitions) { + MONITORING_LOG_ADD(sess->getLocalTag().c_str(), + "dsm_stategraph", + ("> "+ tr->name + " >").c_str()); + } + + if (DSMFactory::MonitoringFullCallgraph) { + MONITORING_LOG_ADD(sess->getLocalTag().c_str(), + "dsm_stategraph", + (current_diag->getName() +"/"+ target_st->name).c_str()); + } #endif - - current = target_st; - - // execute pre-actions - if (current->pre_actions.size()) { - DBG("running %zd pre_actions of state '%s'\n", - current->pre_actions.size(), current->name.c_str()); - if (runactions(current->pre_actions.begin(), - current->pre_actions.end(), - sess, event, event_params, is_consumed)) { - break; + + current = target_st; + + // execute pre-actions + if (current->pre_actions.size()) { + DBG("running %zd pre_actions of state '%s'\n", + current->pre_actions.size(), current->name.c_str()); + if (runactions(current->pre_actions.begin(), + current->pre_actions.end(), + sess, active_event, active_params, is_consumed)) { + break; + } } + + break; } - - break; } + } catch (DSMException& e) { + DBG("DSMException occured, type = %s\n", e.params["type"].c_str()); + is_consumed = false; + + is_exception = true; // continue to process as exception event + exception_params = e.params; + active_params = &exception_params; + active_event = DSMCondition::DSMException; } + } while (!is_consumed); } diff --git a/apps/dsm/DSMStateEngine.h b/apps/dsm/DSMStateEngine.h index 73fd5e02..e219f150 100644 --- a/apps/dsm/DSMStateEngine.h +++ b/apps/dsm/DSMStateEngine.h @@ -73,7 +73,9 @@ class DSMCondition PlaylistSeparator, B2BOtherReply, - B2BOtherBye + B2BOtherBye, + + DSMException }; bool invert; @@ -137,6 +139,8 @@ class DSMTransition vector actions; string from_state; string to_state; + + bool is_exception; }; class DSMModule; @@ -158,6 +162,19 @@ class DSMStateDiagram { const string& getName() { return name; } }; +class DSMException { + public: + DSMException(const string& e_type) + { params["type"] = e_type; } + + DSMException(map& params) + : params(params) { } + + ~DSMException() { } + + map params; +}; + class DSMStateEngine { State* current; DSMStateDiagram* current_diag; @@ -189,7 +206,8 @@ class DSMStateEngine { void runEvent(AmSession* sess, DSMCondition::EventType event, - map* event_params); + map* event_params, + bool run_exception = false); /** @return whether call should be accepted */ bool onInvite(const AmSipRequest& req, DSMSession* sess); diff --git a/apps/dsm/doc/Readme.dsm.txt b/apps/dsm/doc/Readme.dsm.txt index 2c4a5944..1874f301 100644 --- a/apps/dsm/doc/Readme.dsm.txt +++ b/apps/dsm/doc/Readme.dsm.txt @@ -26,8 +26,9 @@ A session (call) in the DonkeySM has a set of named (string) variables. The variables may be used as parameter to most conditions and actions, by prepending the variable name with a dollar sign. The parameters of an event (e.g. the key on key press) may be accessed -by prepending the name with a hash. There are also 'selects' with -which a set of dialog properties can be accessed (e.g. @local_tag). +by prepending the name with a hash (e.g. #key). There are also +'selects' with which a set of dialog properties can be accessed +(e.g. @local_tag). The DonkeySM can be extended by modules, which add new conditions and actions to the language. This way, menuing system etc can be @@ -37,7 +38,14 @@ initialization function that is called when the module is loaded. DonkeySM also has built in actions to call DI methods from other modules. -It can cache a set of prompts, configured at start, in memory +Actions (and conditions) can throw exceptions. Once an exception occurs, +execution of the current actions is interrupted. Exceptions are handled +this way that special "exception" transitions are executed. Exception +transitions are marked with "exception" in the conditions list. Once the +FSM is in exception handling, only exception transitions are followed. +DSMs may throw exceptions with the throw() action. + +DSM can cache a set of prompts, configured at start, in memory using PromptCollection. A patch for fmsc 1.0.4 from the graphical FSM editor fsme @@ -47,6 +55,8 @@ click-n-drag fashion and compiled to SEMS DSM diagrams. DI commands =========== +DI commands allow interaction with DSM calls, and DSM script reload: + postDSMEvent(string call_id, [ [[param0,val0],[param1,val1],...] ] post a DSM event into a call. can be used to interact with running calls in DSM. See DSM + monitoring + DI example in @@ -95,7 +105,7 @@ The DSMStateEngine has a set of DSM diagrams which are loaded by the DSMStateDiagramCollection from text file and interpreted by the DSMChartReader, a simple stack based tokenizing compiler. -DSMDialogs, which implement the DSMSession interface (additionally +DSMCall, which implement the DSMSession interface (additionally to being an AmSession), run DSMStateEngine::runEvent for every event that occurs that should be processed by the engine (e.g. Audio event, onBye, ...). @@ -107,7 +117,7 @@ executed. The DSMCondition::match and DSMAction::execute functions get the event parameters and the session as parameters, so that they can operate on variables, implement selects etc. -The DSMDialog implementation is very simple, it uses a playlist and +The DSMCall implementation is very simple, it uses a playlist and has PromptCollection to simply play prompts etc. DSMCoreModule is a 'built in' module that implements the basic @@ -123,9 +133,6 @@ interface, other session types can easily be implemented, and their functionality be exposed to the DSM interpreter by custom actions and conditions that interact with that specific session type. -Another direction is python/lua/... interpreter module, so that -conditions and actions can be expressed in a more powerful language. - A set of modules exposing more of the core functionality. As the call state representation is nicely encapsulated here, this can @@ -171,7 +178,9 @@ each condition of each transition is way too heavy! SEMS has a dynamically typed type (AmArg), why not use that one for variables? That would also make DI simpler. a patch is very welcome, best to semsdev list: semsdev@iptel.org or - the tracker: http://tracker.iptel.org + the tracker: http://tracker.iptel.org. + There is also the avar array ("AmArg-Var"), which can hold AmArg + variables. some performance numbers? unfortunately not yet for running DSMs. DSM processing is actually fast: diff --git a/apps/dsm/doc/examples/test_exception.dsm b/apps/dsm/doc/examples/test_exception.dsm new file mode 100644 index 00000000..e02f4f6f --- /dev/null +++ b/apps/dsm/doc/examples/test_exception.dsm @@ -0,0 +1,10 @@ + +initial state lobby + enter { + playFile(wav/default_en.wav) + throw(bla,blub=blu;i=somevalue); + }; +transition "thrown error" lobby - exception; test(#blub=="blu"); / + log(1, #i); stop(true) -> end; + +transition "file error" lobby - exception; test(#type="file") / stop(true) -> end;