diff options
author | Stefan Radomski <github@mintwerk.de> | 2016-05-12 13:12:33 (GMT) |
---|---|---|
committer | Stefan Radomski <github@mintwerk.de> | 2016-05-12 13:12:33 (GMT) |
commit | b62e7979600feee23dc7cdb61042a8fc7673122b (patch) | |
tree | f7351372f37979dd2d048e0b68a16a4cd3b2aadb /src/uscxml/Interpreter.cpp | |
parent | 1b11b310be61e51b3ac5ebb83f7c8a33aef3d6e8 (diff) | |
download | uscxml-b62e7979600feee23dc7cdb61042a8fc7673122b.zip uscxml-b62e7979600feee23dc7cdb61042a8fc7673122b.tar.gz uscxml-b62e7979600feee23dc7cdb61042a8fc7673122b.tar.bz2 |
Major Refactoring v2.0
Diffstat (limited to 'src/uscxml/Interpreter.cpp')
-rw-r--r-- | src/uscxml/Interpreter.cpp | 3673 |
1 files changed, 217 insertions, 3456 deletions
diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp index 0fa366b..d6f61a2 100644 --- a/src/uscxml/Interpreter.cpp +++ b/src/uscxml/Interpreter.cpp @@ -20,126 +20,232 @@ #include "uscxml/config.h" #include "uscxml/Common.h" #include "uscxml/Interpreter.h" -#include "uscxml/URL.h" -#include "uscxml/UUID.h" -#include "uscxml/dom/DOMUtils.h" -#include "uscxml/dom/NameSpacingParser.h" -#include "uscxml/transform/FlatStateIdentifier.h" -#include "uscxml/transform/ChartToFSM.h" // only for testing +#include "uscxml/util/DOM.h" +#include "uscxml/util/URL.h" -#include "getopt.h" - -#include "uscxml/plugins/invoker/http/HTTPServletInvoker.h" -#include "uscxml/server/InterpreterServlet.h" -#include "uscxml/concurrency/DelayedEventQueue.h" - -#include <DOM/Simple/DOMImplementation.hpp> -#include <SAX/helpers/InputSourceResolver.hpp> -#include <DOM/io/Stream.hpp> - -#include <boost/lexical_cast.hpp> -#include <boost/tokenizer.hpp> -#include <boost/algorithm/string.hpp> +#include <xercesc/parsers/XercesDOMParser.hpp> +#include <xercesc/framework/MemBufInputSource.hpp> +#include <xercesc/dom/DOM.hpp> +#include <xercesc/sax/HandlerBase.hpp> +#include <xercesc/util/XMLString.hpp> +#include <xercesc/util/PlatformUtils.hpp> +#include "easylogging++.h" -#include <glog/logging.h> #include <iostream> -#include <io/uri.hpp> +#include <boost/algorithm/string.hpp> #include <assert.h> #include <algorithm> +#include <memory> +#include <mutex> -#include "uscxml/Factory.h" - -#if 0 -# define INTERPRETER_IMPL InterpreterDraft6 -# include "uscxml/interpreter/InterpreterDraft6.h" -#elif 1 -# define INTERPRETER_IMPL InterpreterRC -# include "uscxml/interpreter/InterpreterRC.h" -#else -# define INTERPRETER_IMPL InterpreterFast -# include "uscxml/interpreter/InterpreterFast.h" -#endif +#include "getopt.h" + +#include "easylogging++.h" +INITIALIZE_EASYLOGGINGPP #define VERBOSE 0 -/// valid interpreter state transitions -#define VALID_FROM_INSTANTIATED(newState) ( \ - newState == USCXML_MICROSTEPPED || \ - newState == USCXML_INITIALIZED || \ - newState == USCXML_DESTROYED\ -) - -#define VALID_FROM_FAULTED(newState) ( \ - newState == USCXML_DESTROYED\ -) - -#define VALID_FROM_INITIALIZED(newState) ( \ - newState == USCXML_MICROSTEPPED || \ - newState == USCXML_FINISHED || \ - newState == USCXML_DESTROYED \ -) - -#define VALID_FROM_MICROSTEPPED(newState) ( \ - newState == USCXML_DESTROYED || \ - newState == USCXML_MACROSTEPPED || \ - newState == USCXML_MICROSTEPPED || \ - newState == USCXML_FINISHED \ -) - -#define VALID_FROM_MACROSTEPPED(newState) ( \ - newState == USCXML_DESTROYED || \ - newState == USCXML_MICROSTEPPED || \ - newState == USCXML_MACROSTEPPED || \ - newState == USCXML_IDLE || \ - newState == USCXML_FINISHED \ -) - -#define VALID_FROM_IDLE(newState) ( \ - newState == USCXML_DESTROYED || \ - newState == USCXML_MICROSTEPPED || \ - newState == USCXML_MACROSTEPPED \ -) - -#define VALID_FROM_FINISHED(newState) ( \ - newState == USCXML_DESTROYED || \ - newState == USCXML_INSTANTIATED \ -) - -/// macro to catch exceptions in executeContent -#define CATCH_AND_DISTRIBUTE(msg) \ -catch (Event e) {\ - if (rethrow) {\ - throw(e);\ - } else {\ - LOG(ERROR) << msg << std::endl << e << std::endl;\ - e.name = "error.execution";\ - e.data.compound["cause"] = uscxml::Data(msg, uscxml::Data::VERBATIM); \ - e.eventType = Event::PLATFORM;\ - receiveInternal(e);\ - }\ +namespace uscxml { + +// msxml.h defines all the DOM types as well +//using namespace xercesc; + +static URL normalizeURL(const std::string url) { + URL absUrl(url); + + // this is required for _baseURL to be considered absolute! + if (absUrl.scheme() == "" || !absUrl.isAbsolute()) { + absUrl = URL::resolveWithCWD(absUrl); + } + if (absUrl.scheme() == "") { + absUrl = URL("file://" + url); + } + return absUrl; } -#define CATCH_AND_DISTRIBUTE2(msg, node) \ -catch (Event e) {\ - std::string xpathPos = DOMUtils::xPathForNode(node); \ - if (rethrow) {\ - throw(e);\ - } else {\ - LOG(ERROR) << msg << " " << xpathPos << ":" << std::endl << e << std::endl;\ - e.name = "error.execution";\ - e.data.compound["cause"] = uscxml::Data(msg, uscxml::Data::VERBATIM); \ - e.data.compound["xpath"] = uscxml::Data(xpathPos, uscxml::Data::VERBATIM); \ - e.eventType = Event::PLATFORM;\ - e.dom = node;\ - receiveInternal(e);\ - }\ +Interpreter Interpreter::fromXML(const std::string& xml, const std::string& baseURL) { + URL absUrl = normalizeURL(baseURL); + + std::shared_ptr<InterpreterImpl> interpreterImpl(new InterpreterImpl()); + Interpreter interpreter(interpreterImpl); + + std::unique_ptr<xercesc::XercesDOMParser> parser(new xercesc::XercesDOMParser()); + std::unique_ptr<xercesc::ErrorHandler> errHandler(new xercesc::HandlerBase()); + + try { + parser->setValidationScheme(xercesc::XercesDOMParser::Val_Always); + parser->setDoNamespaces(true); + parser->useScanner(xercesc::XMLUni::fgWFXMLScanner); + + parser->setErrorHandler(errHandler.get()); + + xercesc::MemBufInputSource is((XMLByte*)xml.c_str(), xml.size(), X("fake")); + parser->parse(is); + + interpreterImpl->_document = parser->adoptDocument(); + interpreterImpl->_baseURL = absUrl; + InterpreterImpl::addInstance(interpreterImpl); + + } catch (const xercesc::SAXParseException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } catch (const xercesc::RuntimeException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } catch (const xercesc::XMLException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } catch (const xercesc::DOMException& toCatch) { + ERROR_PLATFORM_THROW(X(toCatch.getMessage()).str()); + } + + return interpreter; } -namespace uscxml { +Interpreter Interpreter::fromElement(xercesc::DOMElement* scxml, const std::string& baseURL) { + URL absUrl = normalizeURL(baseURL); + + std::shared_ptr<InterpreterImpl> interpreterImpl(new InterpreterImpl()); + Interpreter interpreter(interpreterImpl); + + // *copy* the given xercesc::DOM to get rid of event listeners + xercesc::DOMImplementation* implementation = xercesc::DOMImplementationRegistry::getDOMImplementation(X("core")); + interpreterImpl->_document = implementation->createDocument(); + + // we need to import the parent - to support xpath test150 + xercesc::DOMNode* newNode = interpreterImpl->_document->importNode(scxml, true); +// interpreterImpl->_document->adoptNode(newNode); + interpreterImpl->_document->appendChild(newNode); + +// std::cerr << *(interpreterImpl->_document); + + interpreterImpl->_baseURL = absUrl; + + InterpreterImpl::addInstance(interpreterImpl); + return interpreter; +} + +Interpreter Interpreter::fromDocument(xercesc::DOMDocument* dom, const std::string& baseURL, bool copy) { + URL absUrl = normalizeURL(baseURL); + + std::shared_ptr<InterpreterImpl> interpreterImpl(new InterpreterImpl()); + Interpreter interpreter(interpreterImpl); + + if (copy) { + // *copy* the given xercesc::DOM to get rid of event listeners + xercesc::DOMImplementation* implementation = xercesc::DOMImplementationRegistry::getDOMImplementation(X("core")); + interpreterImpl->_document = implementation->createDocument(); + + // we need to import the parent - to support xpath test150 + xercesc::DOMNode* newNode = interpreterImpl->_document->importNode(dom->getDocumentElement(), true); + interpreterImpl->_document->appendChild(newNode); + + } else { + interpreterImpl->_document = dom; + } + + interpreterImpl->_baseURL = absUrl; + + InterpreterImpl::addInstance(interpreterImpl); + return interpreter; +} + +Interpreter Interpreter::fromURL(const std::string& url) { + URL absUrl = normalizeURL(url); + + std::shared_ptr<InterpreterImpl> interpreterImpl(new InterpreterImpl()); + Interpreter interpreter(interpreterImpl); + + std::unique_ptr<xercesc::XercesDOMParser> parser(new xercesc::XercesDOMParser()); + parser->setValidationScheme(xercesc::XercesDOMParser::Val_Always); + parser->setDoNamespaces(true); + + // we do not have a real schema anyway + parser->useScanner(xercesc::XMLUni::fgWFXMLScanner); + + std::unique_ptr<xercesc::ErrorHandler> errHandler(new xercesc::HandlerBase()); + parser->setErrorHandler(errHandler.get()); + + + try { + std::string tmp = absUrl; + parser->parse(tmp.c_str()); + interpreterImpl->_document = parser->adoptDocument(); + interpreterImpl->_baseURL = absUrl; + InterpreterImpl::addInstance(interpreterImpl); + } + + catch (const xercesc::SAXParseException& toCatch) { + LOG(ERROR) << X(toCatch.getMessage()); + } catch (const xercesc::RuntimeException& toCatch) { + LOG(ERROR) << X(toCatch.getMessage()); + } catch (const xercesc::XMLException& toCatch) { + LOG(ERROR) << X(toCatch.getMessage()); + } catch (const xercesc::DOMException& toCatch) { + LOG(ERROR) << X(toCatch.getMessage()); + } + + return interpreter; + +} -using namespace Arabica::XPath; -using namespace Arabica::DOM; +std::recursive_mutex StateTransitionMonitor::_mutex; + +static void printNodeSet(const std::list<xercesc::DOMElement*> nodes) { + std::string seperator; + for (auto nIter = nodes.begin(); nIter != nodes.end(); nIter++) { + std::cerr << seperator << (HAS_ATTR(*nIter, "id") ? ATTR(*nIter, "id") : DOMUtils::xPathForNode(*nIter)); + seperator = ", "; + } +} + +void StateTransitionMonitor::beforeTakingTransition(const xercesc::DOMElement* transition) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + std::cerr << "Transition: " << uscxml::DOMUtils::xPathForNode(transition) << std::endl; +} + +void StateTransitionMonitor::onStableConfiguration() { + std::lock_guard<std::recursive_mutex> lock(_mutex); + std::cerr << "Stable Config: { "; + printNodeSet(_interpreter.getConfiguration()); + std::cerr << " }" << std::endl; +} + +void StateTransitionMonitor::beforeProcessingEvent(const uscxml::Event& event) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + switch (event.eventType) { + case uscxml::Event::INTERNAL: + std::cerr << "Internal Event: " << event.name << std::endl; + break; + case uscxml::Event::EXTERNAL: + std::cerr << "External Event: " << event.name << std::endl; + break; + case uscxml::Event::PLATFORM: + std::cerr << "Platform Event: " << event.name << std::endl; + break; + } +} + +void StateTransitionMonitor::beforeExecutingContent(const xercesc::DOMElement* element) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + std::cerr << "Executable Content: " << DOMUtils::xPathForNode(element) << std::endl; +} + +void StateTransitionMonitor::beforeExitingState(const xercesc::DOMElement* state) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + std::cerr << "Exiting: " << (HAS_ATTR(state, "id") ? ATTR(state, "id") : DOMUtils::xPathForNode(state)) << std::endl; +} + +void StateTransitionMonitor::beforeEnteringState(const xercesc::DOMElement* state) { + std::lock_guard<std::recursive_mutex> lock(_mutex); + std::cerr << "Entering: " << (HAS_ATTR(state, "id") ? ATTR(state, "id") : DOMUtils::xPathForNode(state)) << std::endl; + +} + +void StateTransitionMonitor::beforeMicroStep() { + std::lock_guard<std::recursive_mutex> lock(_mutex); + std::cerr << "Config: {"; + printNodeSet(_interpreter.getConfiguration()); + std::cerr << "}" << std::endl; +} void InterpreterOptions::printUsageAndExit(const char* progName) { // remove path from program name @@ -169,7 +275,6 @@ void InterpreterOptions::printUsageAndExit(const char* progName) { #endif printf("\t-v : be verbose\n"); printf("\t-c : perform some sanity checks on the state-chart\n"); - printf("\t-d : enable debugging via HTTP\n"); printf("\t-lN : set loglevel to N\n"); printf("\t-tN : port for HTTP server\n"); printf("\t-sN : port for HTTPS server\n"); @@ -178,14 +283,6 @@ void InterpreterOptions::printUsageAndExit(const char* progName) { exit(1); } -unsigned int InterpreterOptions::getCapabilities() { - unsigned int capabilities = CAN_NOTHING; - if (withHTTP) - capabilities = capabilities | CAN_GENERIC_HTTP | CAN_BASIC_HTTP; - - return capabilities; -} - InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { InterpreterOptions options; optind = 0; @@ -201,7 +298,6 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { {"public-key", required_argument, 0, 0}, {"plugin-path", required_argument, 0, 'p'}, {"loglevel", required_argument, 0, 'l'}, - {"disable-http", no_argument, 0, 0}, {0, 0, 0, 0} }; @@ -234,13 +330,13 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { switch(option) { // cases without short option case 0: { - if (boost::equals(longOptions[optionInd].name, "disable-http")) { + if (iequals(longOptions[optionInd].name, "disable-http")) { currOptions->withHTTP = false; - } else if (boost::equals(longOptions[optionInd].name, "private-key")) { + } else if (iequals(longOptions[optionInd].name, "private-key")) { currOptions->privateKey = optarg; - } else if (boost::equals(longOptions[optionInd].name, "certificate")) { + } else if (iequals(longOptions[optionInd].name, "certificate")) { currOptions->certificate = optarg; - } else if (boost::equals(longOptions[optionInd].name, "public-key")) { + } else if (iequals(longOptions[optionInd].name, "public-key")) { currOptions->publicKey = optarg; } break; @@ -252,11 +348,8 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { case 'p': currOptions->pluginPath = optarg; break; - case 'd': - currOptions->withDebugger = true; - break; case 'c': - currOptions->checking = true; + currOptions->validate = true; break; case 't': currOptions->httpPort = strTo<unsigned short>(optarg); @@ -298,3343 +391,11 @@ InterpreterOptions InterpreterOptions::fromCmdLine(int argc, char** argv) { DONE_PARSING_CMD: - if (options.interpreters.size() == 0 && !options.withDebugger) + if (options.interpreters.size() == 0) options.error = "No SCXML document to evaluate"; return options; } -void NameSpaceInfo::init(const std::map<std::string, std::string>& namespaceInfo) { - nsInfo = namespaceInfo; - nsURL = ""; - if (nsContext) - delete nsContext; - nsContext = new Arabica::XPath::StandardNamespaceContext<std::string>(); - - std::map<std::string, std::string>::const_iterator nsIter = namespaceInfo.begin(); - while(nsIter != namespaceInfo.end()) { - std::string url = nsIter->first; - std::string prefix = nsIter->second; - if (iequals(url, "http://www.w3.org/2005/07/scxml")) { - nsURL = url; - if (prefix.size() == 0) { - xpathPrefix = "scxml:"; - nsContext->addNamespaceDeclaration(url, "scxml"); - } else { - xpathPrefix = prefix + ":"; - xmlNSPrefix = xpathPrefix; - nsContext->addNamespaceDeclaration(url, prefix); - nsToPrefix[url] = prefix; - } - } else { - nsContext->addNamespaceDeclaration(url, prefix); - nsToPrefix[url] = prefix; - } - nsIter++; - } -} - -void StateTransitionMonitor::beforeTakingTransition(uscxml::Interpreter interpreter, const Arabica::DOM::Element<std::string>& transition, bool moreComing) { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - std::cerr << "Transition: " << uscxml::DOMUtils::xPathForNode(transition) << std::endl; -} - -void StateTransitionMonitor::onStableConfiguration(uscxml::Interpreter interpreter) { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - std::cerr << "Config: {"; - printNodeSet(interpreter.getConfiguration()); - std::cerr << "}" << std::endl; -} - -void StateTransitionMonitor::beforeProcessingEvent(uscxml::Interpreter interpreter, const uscxml::Event& event) { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - switch (event.eventType) { - case uscxml::Event::INTERNAL: - std::cerr << "Internal Event: " << event.name << std::endl; - break; - case uscxml::Event::EXTERNAL: - std::cerr << "External Event: " << event.name << std::endl; - break; - case uscxml::Event::PLATFORM: - std::cerr << "Platform Event: " << event.name << std::endl; - break; - } -} - -void StateTransitionMonitor::beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Element<std::string>& element) { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - std::cerr << "Executable Content: " << DOMUtils::xPathForNode(element) << std::endl; -} - -void StateTransitionMonitor::beforeExitingState(uscxml::Interpreter interpreter, const Arabica::DOM::Element<std::string>& state, bool moreComing) { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - exitingStates.push_back(state); - if (!moreComing) { - std::cerr << "Exiting: {"; - printNodeSet(exitingStates); - std::cerr << "}" << std::endl; - exitingStates = Arabica::XPath::NodeSet<std::string>(); - } -} - -void StateTransitionMonitor::beforeEnteringState(uscxml::Interpreter interpreter, const Arabica::DOM::Element<std::string>& state, bool moreComing) { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - enteringStates.push_back(state); - if (!moreComing) { - std::cerr << "Entering: {"; - printNodeSet(enteringStates); - std::cerr << "}" << std::endl; - enteringStates = Arabica::XPath::NodeSet<std::string>(); - } - -} - -void StateTransitionMonitor::beforeMicroStep(uscxml::Interpreter interpreter) { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - std::cerr << "Config: {"; - printNodeSet(interpreter.getConfiguration()); - std::cerr << "}" << std::endl; -} - -void StateTransitionMonitor::printNodeSet(const Arabica::XPath::NodeSet<std::string>& config) { - std::string seperator; - for (size_t i = 0; i < config.size(); i++) { - std::cerr << seperator << ATTR_CAST(config[i], "id"); - seperator = ", "; - } -} -tthread::recursive_mutex StateTransitionMonitor::_mutex; - -std::map<std::string, boost::weak_ptr<InterpreterImpl> > Interpreter::_instances; -tthread::recursive_mutex Interpreter::_instanceMutex; - -std::map<std::string, boost::weak_ptr<InterpreterImpl> > Interpreter::getInstances() { - tthread::lock_guard<tthread::recursive_mutex> lock(_instanceMutex); - std::map<std::string, boost::weak_ptr<InterpreterImpl> >::iterator instIter = _instances.begin(); - while(instIter != _instances.end()) { - if (!instIter->second.lock()) { - _instances.erase(instIter++); - } else { - instIter++; - } - } - return _instances; -} - -void Interpreter::addInstance(boost::shared_ptr<InterpreterImpl> interpreterImpl) { - tthread::lock_guard<tthread::recursive_mutex> lock(_instanceMutex); - assert(_instances.find(interpreterImpl->getSessionId()) == _instances.end()); - _instances[interpreterImpl->getSessionId()] = interpreterImpl; -} - -InterpreterImpl::InterpreterImpl() { - _state = USCXML_INSTANTIATED; - - _lastRunOnMainThread = 0; - _thread = NULL; - _isStarted = false; - _isRunning = false; - _sendQueue = NULL; - _parentQueue = NULL; - _topLevelFinalReached = false; - _stable = false; - _isInitialized = false; - _userSuppliedDataModel = false; - _domIsSetup = false; - _httpServlet = NULL; - _factory = NULL; - _sessionId = UUID::getUUID(); - _capabilities = CAN_BASIC_HTTP | CAN_GENERIC_HTTP; - _domEventListener._interpreter = this; - -#ifdef _WIN32 - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); -#endif -} - -Interpreter Interpreter::fromDOM(const Arabica::DOM::Document<std::string>& dom, const NameSpaceInfo& nameSpaceInfo, const std::string& sourceURL) { - tthread::lock_guard<tthread::recursive_mutex> lock(_instanceMutex); - boost::shared_ptr<INTERPRETER_IMPL> interpreterImpl = boost::shared_ptr<INTERPRETER_IMPL>(new INTERPRETER_IMPL); - Interpreter interpreter(interpreterImpl); - - // *copy* the given DOM to get rid of event listeners - - DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation(); - interpreterImpl->_document = domFactory.createDocument(dom.getNamespaceURI(), "", 0); - - Node<std::string> child = dom.getFirstChild(); - while (child) { - Node<std::string> newNode = interpreterImpl->_document.importNode(child, true); - interpreterImpl->_document.appendChild(newNode); - child = child.getNextSibling(); - } - - interpreterImpl->setNameSpaceInfo(nameSpaceInfo); - interpreterImpl->setupDOM(); - -// interpreterImpl->init(); - _instances[interpreterImpl->getSessionId()] = interpreterImpl; - return interpreter; -} - -Interpreter Interpreter::fromXML(const std::string& xml, const std::string& sourceURL) { - std::stringstream* ss = new std::stringstream(); - (*ss) << xml; - // we need an auto_ptr for arabica to assume ownership - std::auto_ptr<std::istream> ssPtr(ss); - Arabica::SAX::InputSource<std::string> inputSource; - inputSource.setByteStream(ssPtr); - return fromInputSource(&inputSource, sourceURL); -} - - -Interpreter Interpreter::fromURL(const std::string& url) { - URL absUrl(url); - if (!absUrl.isAbsolute()) { - if (!absUrl.toAbsoluteCwd()) { - ERROR_COMMUNICATION_THROW("URL is not absolute or does not have file schema"); - } - } - - Interpreter interpreter; - - if (iequals(absUrl.scheme(), "file")) { - Arabica::SAX::InputSource<std::string> inputSource; - inputSource.setSystemId(absUrl.asString()); - interpreter = fromInputSource(&inputSource, absUrl); - } else { - // use curl for everything !file - even for http as arabica won't follow redirects - std::stringstream ss; - ss << absUrl; - if (absUrl.downloadFailed()) { - ERROR_COMMUNICATION_THROW("Downloading SCXML document from '" + absUrl.asString() + "' failed"); - } - interpreter = fromXML(ss.str(), absUrl); - } - - // try to establish URL root for relative src attributes in document - if (!interpreter) { - ERROR_PLATFORM_THROW("Cannot create interpreter from URL '" + absUrl.asString() + "'"); - } - return interpreter; -} - -Interpreter Interpreter::fromInputSource(void* source, const std::string& sourceURL) { - tthread::lock_guard<tthread::recursive_mutex> lock(_instanceMutex); - - Arabica::SAX::InputSource<std::string> sourceRef = *(Arabica::SAX::InputSource<std::string>*)source; - // remove old instances - std::map<std::string, boost::weak_ptr<InterpreterImpl> >::iterator instIter = _instances.begin(); - while(instIter != _instances.end()) { - if (!instIter->second.lock()) { - _instances.erase(instIter++); - } else { - instIter++; - } - } - - boost::shared_ptr<INTERPRETER_IMPL> interpreterImpl = boost::shared_ptr<INTERPRETER_IMPL>(new INTERPRETER_IMPL); - Interpreter interpreter(interpreterImpl); - _instances[interpreterImpl->getSessionId()] = interpreterImpl; - - NameSpacingParser parser; - if (parser.parse(sourceRef) && parser.getDocument() && parser.getDocument().hasChildNodes()) { - interpreterImpl->setNameSpaceInfo(parser.nameSpace); - interpreterImpl->_document = parser.getDocument(); - interpreterImpl->_sourceURL = sourceURL; - - interpreterImpl->setupDOM(); - } else { - if (parser.errorsReported()) { - ERROR_PLATFORM_THROW(parser.errors()) - } else { - ERROR_PLATFORM_THROW("Failed to create interpreter from " + sourceURL); -// interpreterImpl->setInterpreterState(USCXML_FAULTED, parser.errors()); - } - } - return interpreter; -} - -Interpreter Interpreter::fromClone(const Interpreter& other) { - tthread::lock_guard<tthread::recursive_mutex> lock(_instanceMutex); - - boost::shared_ptr<INTERPRETER_IMPL> interpreterImpl = boost::shared_ptr<INTERPRETER_IMPL>(new INTERPRETER_IMPL); - interpreterImpl->cloneFrom(other.getImpl()); - Interpreter interpreter(interpreterImpl); - - addInstance(interpreterImpl); - return interpreter; -} - -void InterpreterImpl::writeTo(std::ostream& stream) { - stream << getDocument(); -} - -void InterpreterImpl::cloneFrom(InterpreterImpl* other) { - if (other->getDocument()) { - const Arabica::DOM::Document<std::string>& otherDoc = other->_document; - DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation(); - _document = domFactory.createDocument(otherDoc.getNamespaceURI(), "", 0); - - Node<std::string> child = otherDoc.getFirstChild(); - while (child) { - Node<std::string> newNode = _document.importNode(child, true); - _document.appendChild(newNode); - child = child.getNextSibling(); - } - - setNameSpaceInfo(other->_nsInfo); - - _baseURL = other->_baseURL; - _sourceURL = other->_sourceURL; - - setupDOM(); - } -} - -void InterpreterImpl::cloneFrom(boost::shared_ptr<InterpreterImpl> other) { - cloneFrom(other.get()); -} - -void InterpreterImpl::setName(const std::string& name) { - _name = name; -} - -InterpreterImpl::~InterpreterImpl() { - { - // make sure we are done with setting up with early abort - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - stop(); // unset started bit - - setInterpreterState(USCXML_DESTROYED); - - // unblock event queue - Event event; - event.name = "unblock.and.die"; - receive(event); - - } -// std::cerr << "stopped " << this << std::endl; -// tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - if (_thread) { - _thread->join(); - delete(_thread); - } - - if (_sendQueue) - delete _sendQueue; - -} - -void InterpreterImpl::start() { - _isStarted = true; - _thread = new tthread::thread(InterpreterImpl::run, this); -} - -void InterpreterImpl::stop() { - _isStarted = false; -} - -void InterpreterImpl::join() { - stop(); - if (_thread != NULL) _thread->join(); -}; - -bool InterpreterImpl::isRunning() { - return _isRunning && !_topLevelFinalReached; -} - -void InterpreterImpl::run(void* instance) { - InterpreterImpl* interpreter = ((InterpreterImpl*)instance); - interpreter->_isRunning = true; - - try { - InterpreterState state; - while(interpreter->_isStarted) { - state = interpreter->step(-1); - - switch (state) { - case uscxml::USCXML_FINISHED: - case uscxml::USCXML_DESTROYED: - // return as we finished - goto DONE_THREAD; - default: - break; - } - } - } catch (Event e) { - LOG(ERROR) << e; - } catch(std::exception e) { - LOG(ERROR) << "InterpreterImpl::run catched an exception: " << e.what() << std::endl << "Unclean shutdown"; - } catch (...) { - LOG(ERROR) << "InterpreterImpl::run catched unknown exception"; - } -DONE_THREAD: - ((InterpreterImpl*)instance)->_isRunning = false; - ((InterpreterImpl*)instance)->_isStarted = false; -} - -void InterpreterImpl::exitInterpreter() { - NodeSet<std::string> statesToExit = _configuration; - statesToExit.forward(false); - statesToExit.sort(); - - for (size_t i = 0; i < statesToExit.size(); i++) { - Arabica::XPath::NodeSet<std::string> onExitElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "onexit", statesToExit[i]); - for (size_t j = 0; j < onExitElems.size(); j++) { - executeContent(Element<std::string>(onExitElems[j])); - } - Arabica::XPath::NodeSet<std::string> invokeElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", statesToExit[i]); - // TODO: we ought to cancel all remaining invokers just to be sure with the persist extension - for (size_t j = 0; j < invokeElems.size(); j++) { - cancelInvoke(Element<std::string>(invokeElems[j])); - } - Element<std::string> stateElem(statesToExit[i]); - if (isFinal(stateElem) && parentIsScxmlState(stateElem)) { - returnDoneEvent(statesToExit[i]); - } - } - _configuration = NodeSet<std::string>(); -} - - -InterpreterState InterpreterImpl::interpret() { - InterpreterState state; - while(true) { - state = step(-1); - - switch (state) { - case uscxml::USCXML_FINISHED: - case uscxml::USCXML_DESTROYED: - // return as we finished - return state; - default: - - // process invokers on main thread - if(_thread == NULL) { - runOnMainThread(200); - } - - // process next step - break; - } - } - return state; -} - -// setup / fetch the documents initial transitions -NodeSet<std::string> InterpreterImpl::getDocumentInitialTransitions() { - NodeSet<std::string> initialTransitions; - - if (_startConfiguration.size() > 0) { - // we emulate entering a given configuration by creating a pseudo deep history - Element<std::string> initHistory = _document.createElementNS(_nsInfo.nsURL, "history"); - _nsInfo.setPrefix(initHistory); - - initHistory.setAttribute("id", UUID::getUUID()); - initHistory.setAttribute("type", "deep"); - _scxml.insertBefore(initHistory, _scxml.getFirstChild()); - - std::string histId = ATTR(initHistory, "id"); - NodeSet<std::string> histStates; - for (std::list<std::string>::const_iterator stateIter = _startConfiguration.begin(); stateIter != _startConfiguration.end(); stateIter++) { - histStates.push_back(getState(*stateIter)); - } - _historyValue[histId] = histStates; - - Element<std::string> initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); - _nsInfo.setPrefix(initialElem); - - initialElem.setAttribute("generated", "true"); - Element<std::string> transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); - _nsInfo.setPrefix(transitionElem); - - transitionElem.setAttribute("target", histId); - initialElem.appendChild(transitionElem); - _scxml.appendChild(initialElem); - initialTransitions.push_back(transitionElem); - - } else { - // try to get initial transition from initial element - initialTransitions = _xpath.evaluate("/" + _nsInfo.xpathPrefix + "scxml/"+ _nsInfo.xpathPrefix + "initial/" + _nsInfo.xpathPrefix + "transition", _scxml).asNodeSet(); - if (initialTransitions.size() == 0) { - Arabica::XPath::NodeSet<std::string> initialStates; - // fetch per draft - initialStates = getInitialStates(); - assert(initialStates.size() > 0); - for (size_t i = 0; i < initialStates.size(); i++) { - Element<std::string> initialElem = _document.createElementNS(_nsInfo.nsURL, "initial"); - _nsInfo.setPrefix(initialElem); - - initialElem.setAttribute("generated", "true"); - Element<std::string> transitionElem = _document.createElementNS(_nsInfo.nsURL, "transition"); - _nsInfo.setPrefix(transitionElem); - - transitionElem.setAttribute("target", ATTR_CAST(initialStates[i], "id")); - initialElem.appendChild(transitionElem); - _scxml.appendChild(initialElem); - initialTransitions.push_back(transitionElem); - } - } - } - return initialTransitions; -} - -InterpreterState InterpreterImpl::step(int waitForMS) { - try { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - - if (_state == USCXML_FINISHED || _state == USCXML_DESTROYED) { - return _state; - } - - NodeSet<std::string> enabledTransitions; - - // setup document and interpreter - if (!_isInitialized) { - init(); // will throw - return _state; - } - - if (_configuration.size() == 0) { - // goto initial configuration - NodeSet<std::string> initialTransitions = getDocumentInitialTransitions(); - assert(initialTransitions.size() > 0); -#if VERBOSE - std::cerr << _name << ": initialTransitions: " << std::endl; - for (size_t i = 0; i < initialTransitions.size(); i++) { - std::cerr << initialTransitions[i] << std::endl; - } - std::cerr << std::endl; -#endif - - // this is not mentionend in the standard, though it makes sense - for (size_t i = 0; i < initialTransitions.size(); i++) { - Element<std::string> transition(initialTransitions[i]); - USCXML_MONITOR_CALLBACK3(beforeTakingTransition, transition, (i + 1 < enabledTransitions.size())) - executeContent(transition); - USCXML_MONITOR_CALLBACK3(afterTakingTransition, transition, (i + 1 < enabledTransitions.size())) - } - - enterStates(initialTransitions); - setInterpreterState(USCXML_MICROSTEPPED); - } - - assert(isLegalConfiguration(_configuration)); - - // are there spontaneous transitions? - if (!_stable) { - enabledTransitions = selectEventlessTransitions(); - if (!enabledTransitions.empty()) { - // test 403b - enabledTransitions.to_document_order(); - microstep(enabledTransitions); - - setInterpreterState(USCXML_MICROSTEPPED); - - // check whether we run in cycles - FlatStateIdentifier flat(_configuration, _alreadyEntered, _historyValue); - if (_microstepConfigurations.find(flat.getStateId()) != _microstepConfigurations.end()) { - USCXML_MONITOR_CALLBACK2(reportIssue, - InterpreterIssue("Reentering during microstep " + flat.getFlatActive() + " - possible endless loop", - Arabica::DOM::Node<std::string>(), - InterpreterIssue::USCXML_ISSUE_WARNING)); - } - _microstepConfigurations.insert(flat.getStateId()); - - return _state; - } - _stable = true; - } - - // test415 - if (_topLevelFinalReached) - goto EXIT_INTERPRETER; - - // process internal event - if (!_internalQueue.empty()) { - _currEvent = _internalQueue.front(); - _internalQueue.pop_front(); - _stable = false; - - USCXML_MONITOR_CALLBACK2(beforeProcessingEvent, _currEvent) - - _dataModel.setEvent(_currEvent); - enabledTransitions = selectTransitions(_currEvent.name); - - if (!enabledTransitions.empty()) { - // test 403b - enabledTransitions.to_document_order(); - microstep(enabledTransitions); - } - - // test 319 - even if we do not enable transitions, consider it a microstep - setInterpreterState(USCXML_MICROSTEPPED); - return _state; - - } else { - _stable = true; - _microstepConfigurations.clear(); - } - - if (_state != USCXML_MACROSTEPPED && _state != USCXML_IDLE) - USCXML_MONITOR_CALLBACK(onStableConfiguration) - - setInterpreterState(USCXML_MACROSTEPPED); - - if (_topLevelFinalReached) - goto EXIT_INTERPRETER; - - - // when we reach a stable configuration, invoke - for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { - NodeSet<std::string> invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); - for (unsigned int j = 0; j < invokes.size(); j++) { - Element<std::string> invokeElem = Element<std::string>(invokes[j]); - if (!HAS_ATTR(invokeElem, "persist") || !stringIsTrue(ATTR(invokeElem, "persist"))) { - invoke(invokeElem); - } - } - } - _statesToInvoke = NodeSet<std::string>(); - - if (_externalQueue.isEmpty()) { - setInterpreterState(USCXML_IDLE); - - if (waitForMS < 0) { - // wait blockingly for an event forever - while(_externalQueue.isEmpty()) { - _condVar.wait(_mutex); - } - } - - if (waitForMS > 0) { - // wait given number of milliseconds max - uint64_t now = tthread::chrono::system_clock::now(); - uint64_t then = now + waitForMS; - while(_externalQueue.isEmpty() && now < then) { - _condVar.wait_for(_mutex, then - now); - now = tthread::chrono::system_clock::now(); - } - } - - if (_externalQueue.isEmpty()) { - return _state; - } - - if (_state == USCXML_FINISHED || _state == USCXML_DESTROYED) { - return _state; - } - - setInterpreterState(USCXML_MACROSTEPPED); - } - - _currEvent = _externalQueue.pop(); - _currEvent.eventType = Event::EXTERNAL; // make sure it is set to external - _stable = false; - - if (_topLevelFinalReached) - goto EXIT_INTERPRETER; - - USCXML_MONITOR_CALLBACK2(beforeProcessingEvent, _currEvent) - - if (iequals(_currEvent.name, "cancel.invoke." + _sessionId)) { - goto EXIT_INTERPRETER; - } - - try { - _dataModel.setEvent(_currEvent); - } catch (Event e) { - LOG(ERROR) << "Syntax error while setting external event:" << std::endl << e << std::endl << _currEvent; - } - - finalizeAndAutoForwardCurrentEvent(); - - // run internal processing until we reach a stable configuration again - enabledTransitions = selectTransitions(_currEvent.name); - if (!enabledTransitions.empty()) { - // test 403b - enabledTransitions.to_document_order(); - microstep(enabledTransitions); - } - - if (_topLevelFinalReached) - goto EXIT_INTERPRETER; - - return _state; - -EXIT_INTERPRETER: - USCXML_MONITOR_CALLBACK(beforeCompletion) - - exitInterpreter(); - if (_sendQueue) { - _sendQueue->stop(); - std::map<std::string, std::pair<InterpreterImpl*, SendRequest> >::iterator sendIter = _sendIds.begin(); - while(sendIter != _sendIds.end()) { - _sendQueue->cancelEvent(sendIter->first); - sendIter++; - } - _sendQueue->start(); - } - - USCXML_MONITOR_CALLBACK(afterCompletion) - - // assert(hasLegalConfiguration()); - setInterpreterState(USCXML_FINISHED); - _mutex.unlock(); - - // remove datamodel -// if(!_userSuppliedDataModel) -// _dataModel = DataModel(); - - return _state; - } catch (boost::bad_weak_ptr e) { - LOG(ERROR) << "Unclean shutdown " << std::endl << std::endl; - setInterpreterState(USCXML_DESTROYED); - return _state; - } - - // set datamodel to null from this thread - if(_dataModel) - _dataModel = DataModel(); - -} - -void InterpreterImpl::microstep(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { - - USCXML_MONITOR_CALLBACK(beforeMicroStep) - - exitStates(enabledTransitions); - - for (size_t i = 0; i < enabledTransitions.size(); i++) { - Element<std::string> transition(enabledTransitions[i]); - - USCXML_MONITOR_CALLBACK3(beforeTakingTransition, transition, (i + 1 < enabledTransitions.size())) - - executeContent(transition); - - USCXML_MONITOR_CALLBACK3(afterTakingTransition, transition, (i + 1 < enabledTransitions.size())) - } - - enterStates(enabledTransitions); - - USCXML_MONITOR_CALLBACK(afterMicroStep) - -} - -// process transitions until we are in a stable configuration again -void InterpreterImpl::stabilize() { - - NodeSet<std::string> enabledTransitions; - _stable = false; - - if (_configuration.size() == 0) { - // goto initial configuration - NodeSet<std::string> initialTransitions = getDocumentInitialTransitions(); - assert(initialTransitions.size() > 0); - enterStates(initialTransitions); - } - - std::set<std::string> configurationsSeen; - - do { // process microsteps for enabled transitions until there are no more left - - enabledTransitions = selectEventlessTransitions(); - - if (enabledTransitions.size() == 0) { - if (_internalQueue.size() == 0) { - _stable = true; - } else { - _currEvent = _internalQueue.front(); - _internalQueue.pop_front(); -#if VERBOSE - std::cerr << "Received internal event " << _currEvent.name << std::endl; -#endif - - USCXML_MONITOR_CALLBACK2(beforeProcessingEvent, _currEvent) - - if (_dataModel) - _dataModel.setEvent(_currEvent); - enabledTransitions = selectTransitions(_currEvent.name); - } - } - - if (!enabledTransitions.empty()) { - // test 403b - enabledTransitions.to_document_order(); - microstep(enabledTransitions); - } - } while(!_internalQueue.empty() || !_stable); - - USCXML_MONITOR_CALLBACK(onStableConfiguration) - - // when we reach a stable configuration, invoke - for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { - NodeSet<std::string> invokes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "invoke", _statesToInvoke[i]); - for (unsigned int j = 0; j < invokes.size(); j++) { - Element<std::string> invokeElem = Element<std::string>(invokes[j]); - if (!HAS_ATTR(invokeElem, "persist") || !stringIsTrue(ATTR(invokeElem, "persist"))) { - invoke(invokeElem); - } - } - } - _statesToInvoke = NodeSet<std::string>(); -} - -#if 0 - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::selectTransitions(const std::string& event) { - Arabica::XPath::NodeSet<std::string> enabledTransitions; - - NodeSet<std::string> states; - for (unsigned int i = 0; i < _configuration.size(); i++) { - if (isAtomic(Element<std::string>(_configuration[i]))) - states.push_back(_configuration[i]); - } - states.to_document_order(); - - unsigned int index = 0; - while(states.size() > index) { - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", states[index]); - for (unsigned int k = 0; k < transitions.size(); k++) { - if (isEnabledTransition(Element<std::string>(transitions[k]), event)) { - enabledTransitions.push_back(transitions[k]); - goto LOOP; - } - } - { - Node<std::string> parent = states[index].getParentNode(); - if (parent) { - states.push_back(parent); - } - } -LOOP: - index++; - } - - enabledTransitions = removeConflictingTransitions(enabledTransitions); - return enabledTransitions; -} - - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::selectEventlessTransitions() { - Arabica::XPath::NodeSet<std::string> enabledTransitions; - - NodeSet<std::string> states; - for (unsigned int i = 0; i < _configuration.size(); i++) { - if (isAtomic(Element<std::string>(_configuration[i]))) - states.push_back(_configuration[i]); - } - states.to_document_order(); - - unsigned int index = 0; - while(states.size() > index) { - bool foundTransition = false; - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", states[index]); - for (unsigned int k = 0; k < transitions.size(); k++) { - Element<std::string> transElem(transitions[k]); - if (!HAS_ATTR(transElem, "event") && hasConditionMatch(transElem)) { - enabledTransitions.push_back(transitions[k]); - foundTransition = true; - goto LOOP; - } - } - if (!foundTransition) { - Node<std::string> parent = states[index].getParentNode(); - if (parent) { - states.push_back(parent); - } - } -LOOP: - index++; - } - -#if VERBOSE - std::cerr << "Enabled eventless transitions: " << std::endl; - for (size_t i = 0; i < enabledTransitions.size(); i++) { - std::cerr << enabledTransitions[i] << std::endl << "----" << std::endl; - } - std::cerr << std::endl; -#endif - - enabledTransitions = removeConflictingTransitions(enabledTransitions); - return enabledTransitions; -} - -#else - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::selectTransitions(const std::string& event) { - Arabica::XPath::NodeSet<std::string> enabledTransitions; - - NodeSet<std::string> atomicStates; - for (unsigned int i = 0; i < _configuration.size(); i++) { - if (isAtomic(Element<std::string>(_configuration[i]))) - atomicStates.push_back(_configuration[i]); - } - atomicStates.to_document_order(); - - for (unsigned int i = 0; i < atomicStates.size(); i++) { - Element<std::string> state(atomicStates[i]); - while(true) { - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", state); - for (unsigned int k = 0; k < transitions.size(); k++) { - if (isEnabledTransition(Element<std::string>(transitions[k]), event)) { - enabledTransitions.push_back(transitions[k]); - goto NEXT_ATOMIC; - } - } - if (state.getParentNode() && state.getParentNode().getNodeType() == Node_base::ELEMENT_NODE) { - state = Element<std::string>(state.getParentNode()); - } else { - goto NEXT_ATOMIC; - } - } -NEXT_ATOMIC: - ; - } - -#if 0 - std::cerr << "Enabled transitions for '" << event << "': " << std::endl; - for (size_t i = 0; i < enabledTransitions.size(); i++) { - std::cerr << enabledTransitions[i] << std::endl << "----" << std::endl; - } - std::cerr << std::endl; -#endif - - enabledTransitions = removeConflictingTransitions(enabledTransitions); - -#if 0 - std::cerr << "Non-conflicting transitions for '" << event << "': " << std::endl; - for (size_t i = 0; i < enabledTransitions.size(); i++) { - std::cerr << enabledTransitions[i] << std::endl << "----" << std::endl; - } - std::cerr << std::endl; -#endif - - return enabledTransitions; -} - - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::selectEventlessTransitions() { - - Arabica::XPath::NodeSet<std::string> enabledTransitions; - - NodeSet<std::string> atomicStates; - for (unsigned int i = 0; i < _configuration.size(); i++) { - if (isAtomic(Element<std::string>(_configuration[i]))) - atomicStates.push_back(_configuration[i]); - } - atomicStates.to_document_order(); - - for (unsigned int i = 0; i < atomicStates.size(); i++) { - Element<std::string> state(atomicStates[i]); - while(true) { - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", state); - for (unsigned int k = 0; k < transitions.size(); k++) { - Element<std::string> transElem(transitions[k]); - if (!HAS_ATTR(transElem, "event") && hasConditionMatch(transElem)) { - enabledTransitions.push_back(transitions[k]); - goto NEXT_ATOMIC; - } - } - if (state.getParentNode() && state.getParentNode().getNodeType() == Node_base::ELEMENT_NODE) { - state = Element<std::string>(state.getParentNode()); - } else { - goto NEXT_ATOMIC; - } - } -NEXT_ATOMIC: - ; - } - -#if 0 - std::cerr << "Enabled eventless transitions: " << std::endl; - for (size_t i = 0; i < enabledTransitions.size(); i++) { - std::cerr << enabledTransitions[i] << std::endl << "----" << std::endl; - } - std::cerr << std::endl; -#endif - - enabledTransitions = removeConflictingTransitions(enabledTransitions); - return enabledTransitions; -} - - -#endif - -bool InterpreterImpl::isEnabledTransition(const Element<std::string>& transition, const std::string& event) { - std::string eventName; - if (HAS_ATTR(transition, "event")) { - eventName = ATTR(transition, "event"); - } else if(HAS_ATTR(transition, "eventexpr")) { - if (_dataModel) { - eventName = _dataModel.evalAsString(ATTR(transition, "eventexpr")); - } else { - LOG(ERROR) << "Transition has eventexpr attribute with no datamodel defined"; - return false; - } - } else { - return false; - } - - std::list<std::string> eventNames = tokenize(eventName); - std::list<std::string>::iterator eventIter = eventNames.begin(); - while(eventIter != eventNames.end()) { - if(nameMatch(*eventIter, event) && hasConditionMatch(transition)) { - return true; - } - eventIter++; - } - return false; -} - - -InterpreterState InterpreterImpl::getInterpreterState() { - return _state; -} - -void InterpreterImpl::setInterpreterState(InterpreterState newState) { - switch (_state) { - case USCXML_INSTANTIATED: - if (VALID_FROM_INSTANTIATED(newState)) - break; - assert(false); - break; - case USCXML_INITIALIZED: - if (VALID_FROM_INITIALIZED(newState)) - break; - assert(false); - break; - case USCXML_MICROSTEPPED: - if (VALID_FROM_MICROSTEPPED(newState)) - break; - assert(false); - break; - case USCXML_MACROSTEPPED: - if (VALID_FROM_MACROSTEPPED(newState)) - break; - assert(false); - break; - case USCXML_IDLE: - if (VALID_FROM_IDLE(newState)) - break; - assert(false); - break; - case USCXML_FINISHED: - if (VALID_FROM_FINISHED(newState)) - break; - assert(false); - break; - default: - assert(false); - break; - } - - _state = newState; -} - -bool InterpreterImpl::runOnMainThread(int fps, bool blocking) { - if (_state == USCXML_FINISHED || _state == USCXML_DESTROYED || !_isStarted) - return false; - - if (fps > 0) { - if (blocking && _lastRunOnMainThread > 0) { - uint64_t nextRun = _lastRunOnMainThread + (1000 / fps); - while(nextRun > tthread::timeStamp()) { - tthread::this_thread::sleep_for(tthread::chrono::milliseconds(nextRun - tthread::timeStamp())); - } - } else { - _lastRunOnMainThread = tthread::timeStamp(); - return true; - } - } - - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - _lastRunOnMainThread = tthread::timeStamp(); - - { - tthread::lock_guard<tthread::recursive_mutex> lock(_pluginMutex); - std::map<std::string, IOProcessor>::iterator ioProcessorIter = _ioProcessors.begin(); - while(ioProcessorIter != _ioProcessors.end()) { - ioProcessorIter->second.runOnMainThread(); - ioProcessorIter++; - } - std::map<std::string, Invoker>::iterator invokerIter = _invokers.begin(); - while(invokerIter != _invokers.end()) { - invokerIter->second.runOnMainThread(); - invokerIter++; - } - } - return (_thread != NULL); -} - -void InterpreterImpl::reset() { - tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); - - if (_sendQueue) { - _sendQueue->stop(); - std::map<std::string, std::pair<InterpreterImpl*, SendRequest> >::iterator sendIter = _sendIds.begin(); - while(sendIter != _sendIds.end()) { - _sendQueue->cancelEvent(sendIter->first); - _sendIds.erase(sendIter++); - } - _sendQueue->start(); - } - std::map<std::string, Invoker>::iterator invokeIter = _invokers.begin(); - while(invokeIter != _invokers.end()) { - invokeIter->second.uninvoke(); - _invokers.erase(invokeIter++); - } - - _externalQueue.clear(); - _internalQueue.clear(); - _historyValue.clear(); - - _currEvent = Event(); - _alreadyEntered = NodeSet<std::string>(); - _configuration = NodeSet<std::string>(); - _topLevelFinalReached = false; - _isInitialized = false; - _stable = false; - - _dataModel = DataModel(); - setInterpreterState(USCXML_INSTANTIATED); -} - -std::list<InterpreterIssue> InterpreterImpl::validate() { - return InterpreterIssue::forInterpreter(this); -} - -std::ostream& operator<< (std::ostream& os, const InterpreterIssue& issue) { - switch (issue.severity) { - case InterpreterIssue::USCXML_ISSUE_FATAL: - os << "Issue (FATAL) "; - break; - case InterpreterIssue::USCXML_ISSUE_WARNING: - os << "Issue (WARNING) "; - break; - case InterpreterIssue::USCXML_ISSUE_INFO: - os << "Issue (INFO) "; - break; - default: - break; - } - - if (issue.xPath.size() > 0) { - os << "at " << issue.xPath << ": "; - } else { - os << ": "; - } - os << issue.message; - return os; -} - -void InterpreterImpl::setupDOM() { - if (_domIsSetup) - return; - - if (!_document) { - ERROR_PLATFORM_THROW("Interpreter has no DOM"); - } - - resolveXIncludes(); - - // find scxml element - if (!_scxml) { - NodeList<std::string> scxmls; - if (_nsInfo.nsURL.size() == 0) { - scxmls = _document.getElementsByTagName("scxml"); - } else { - scxmls = _document.getElementsByTagNameNS(_nsInfo.nsURL, "scxml"); - } - - if (scxmls.getLength() == 0) { - ERROR_PLATFORM_THROW("Cannot find SCXML element in DOM"); - } - - _scxml = (Arabica::DOM::Element<std::string>)scxmls.item(0); - _baseURL[_scxml] = URL::asBaseURL(_sourceURL); - } - - _binding = (HAS_ATTR(_scxml, "binding") && iequals(ATTR(_scxml, "binding"), "late") ? LATE : EARLY); - - if (_nsInfo.getNSContext() != NULL) - _xpath.setNamespaceContext(*_nsInfo.getNSContext()); - - // normalize document - - // make sure every state has an id - not required per spec, but needed for us - Arabica::XPath::NodeSet<std::string> states = getAllStates(); - for (size_t i = 0; i < states.size(); i++) { - Arabica::DOM::Element<std::string> stateElem = Arabica::DOM::Element<std::string>(states[i]); - if (!stateElem.hasAttribute("id")) { - stateElem.setAttribute("id", UUID::getUUID()); - } - // Issue 64 - we may not cache them as they may be in an embedded document - if (!isInEmbeddedDocument(stateElem)) - _cachedStates[ATTR(stateElem, "id")] = stateElem; - } - - // make sure every invoke has an idlocation or id - actually required! - Arabica::XPath::NodeSet<std::string> invokes = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "invoke", _scxml).asNodeSet(); - for (size_t i = 0; i < invokes.size(); i++) { - Arabica::DOM::Element<std::string> invokeElem = Arabica::DOM::Element<std::string>(invokes[i]); - if (!invokeElem.hasAttribute("id") && !invokeElem.hasAttribute("idlocation")) { - invokeElem.setAttribute("id", UUID::getUUID()); - } - } - -#if 0 - // add an id to the scxml element - if (!_scxml.hasAttribute("id")) { - _scxml.setAttribute("id", UUID::getUUID()); - } -#endif - - // register for dom events to manage cached states - Arabica::DOM::Events::EventTarget<std::string> eventTarget(_scxml); - eventTarget.addEventListener("DOMNodeInserted", _domEventListener, true); - eventTarget.addEventListener("DOMNodeRemoved", _domEventListener, true); - eventTarget.addEventListener("DOMSubtreeModified", _domEventListener, true); - _domIsSetup = true; -} - -void InterpreterImpl::resolveXIncludes() { - std::string xIncludeNS = _nsInfo.getXMLPrefixForNS("http://www.w3.org/2001/XInclude"); - - // no element in namespace for xinclude, don't bother searching - if (xIncludeNS.size() == 0) - return; - - std::map<std::string, std::string> mergedNs = _nsInfo.nsInfo; - - std::list<std::string> includeChain; - includeChain.push_back(_sourceURL); - - Arabica::XPath::NodeSet<std::string> xincludes = _xpath.evaluate("//" + xIncludeNS + "include", _document.getDocumentElement()).asNodeSet(); - for (size_t i = 0; i < xincludes.size(); i++) { - // recursively resolve includes - resolveXIncludes(includeChain, mergedNs, xIncludeNS, URL::asBaseURL(_sourceURL), Element<std::string>(xincludes[i])); - } - - // update NameSpaceInfo and reinit xpath resolver - _nsInfo = NameSpaceInfo(mergedNs); - _xpath.setNamespaceContext(*_nsInfo.getNSContext()); - -} - -void InterpreterImpl::resolveXIncludes(std::list<std::string> includeChain, - std::map<std::string, std::string>& mergedNS, - const std::string& xIncludeNS, - const URL& baseURL, - const Arabica::DOM::Element<std::string>& xinclude) { - URL src = baseURL; - NodeSet<std::string> newNodes; - - if (HAS_ATTR(xinclude, "href")) { - src = URL(ATTR(xinclude, "href")); - if (!src.isAbsolute()) { - if (!src.toAbsolute(baseURL)) { - LOG(ERROR) << "Cannot resolve relative URL '" << ATTR(xinclude, "href") << "' to absolute URL via base URL '" << baseURL << "', trying xi:fallback"; - goto TRY_WITH_FALLBACK; - } - } - - if (std::find(includeChain.begin(), includeChain.end(), src.asString()) != includeChain.end()) { - std::stringstream incErr; - incErr << ("Ignoring recursive inclusion of '" + src.asString() + " via:") << std::endl; - for (std::list<std::string>::iterator incIter = includeChain.begin(); incIter != includeChain.end(); incIter++) { - incErr << " " << *incIter << std::endl; - } - LOG(ERROR) << incErr.str(); - return; - } - includeChain.push_back(src.asString()); - - if (HAS_ATTR(xinclude, "accept")) { - src.addOutHeader("Accept", ATTR_CAST(xinclude, "accept")); - } - - if (HAS_ATTR(xinclude, "accept-language")) { - src.addOutHeader("Accept-Language", ATTR_CAST(xinclude, "accept-language")); - } - - std::string includedContent; - try { - includedContent = src.getInContent(); - } catch (Event e) { - goto TRY_WITH_FALLBACK; - } - - if (HAS_ATTR(xinclude, "parse") && iequals(ATTR(xinclude, "parse"), "text")) { - // parse as text - Text<std::string> textNode = _document.createTextNode(includedContent); - xinclude.getParentNode().insertBefore(textNode, xinclude); - goto REMOVE_AND_RECURSE; - } else { - // parse as XML - NameSpacingParser xiParser = NameSpacingParser::fromXML(includedContent); - if (xiParser.errorsReported()) { - ERROR_PLATFORM_THROW(xiParser.errors()); - } else { - // scxml namespace prefixed and non-profixed - if (mergedNS.find("http://www.w3.org/2005/07/scxml") != mergedNS.end() && xiParser.nameSpace.find("http://www.w3.org/2005/07/scxml") == xiParser.nameSpace.end()) { - LOG(WARNING) << ("Warning for '" + DOMUtils::xPathForNode(xinclude) + "': root document maps SCXML namespace to prefix '" + mergedNS["http://www.w3.org/2005/07/scxml"] + "', included document does not specify SCXML namespace at all"); - } - if (mergedNS.find("http://www.w3.org/2005/07/scxml") == mergedNS.end() && xiParser.nameSpace.find("http://www.w3.org/2005/07/scxml") != xiParser.nameSpace.end()) { - LOG(ERROR) << ("Error for '" + DOMUtils::xPathForNode(xinclude) + "': root document uses implicit SCXML namespace without prefix, included document does map it to prefix '" + xiParser.nameSpace["http://www.w3.org/2005/07/scxml"] + "', trying xi:fallback"); - goto TRY_WITH_FALLBACK; - - } - - // merge namespaces to prefix mappings - for (std::map<std::string, std::string>::iterator nsIter = xiParser.nameSpace.begin(); nsIter != xiParser.nameSpace.end(); nsIter++) { - - // same nsURL but different prefix - if (mergedNS.find(nsIter->first) != mergedNS.end() && mergedNS[nsIter->first] != nsIter->second) { - LOG(ERROR) << ("Error for '" + DOMUtils::xPathForNode(xinclude) + "': Cannot map namespace '" + nsIter->first + "' to prefix '" + nsIter->second + "', it is already mapped to prefix '" + mergedNS[nsIter->first] + "', trying xi:fallback"); - goto TRY_WITH_FALLBACK; - } - - // same prefix but different nsURL - for (std::map<std::string, std::string>::iterator currIter = mergedNS.begin(); currIter != mergedNS.end(); currIter++) { - if (currIter->second == nsIter->second && currIter->first != nsIter->first) { - LOG(ERROR) << ("Error for '" + DOMUtils::xPathForNode(xinclude) + "': Cannot assign prefix '" + nsIter->second + "' to namespace '" + nsIter->first + "' it is already a prefix for '" + currIter->first + "', trying xi:fallback"); - goto TRY_WITH_FALLBACK; - } - } - mergedNS[nsIter->first] = nsIter->second; - } - - // import DOM - Node<std::string> imported = _document.importNode(xiParser.getDocument().getDocumentElement(), true); - newNodes.push_back(imported); - xinclude.getParentNode().insertBefore(imported, xinclude); - goto REMOVE_AND_RECURSE; - } - } - } else { - LOG(ERROR) << "No href attribute for xi:xinclude at '" << DOMUtils::xPathForNode(xinclude) << "', trying xi:fallback"; - goto TRY_WITH_FALLBACK; - } -TRY_WITH_FALLBACK: { - NodeSet<std::string> fallbacks = DOMUtils::filterChildElements(xIncludeNS + "fallback", xinclude); - if (fallbacks.size() > 0) { - LOG(WARNING) << "Using xi:fallback for '" << DOMUtils::xPathForNode(xinclude) << "'"; - // move the fallbacks children in place - NodeList<std::string> fallbackChildren = fallbacks[0].getChildNodes(); - while (fallbacks[0].getChildNodes().getLength() > 0) { - newNodes.push_back(fallbackChildren.item(0)); - xinclude.getParentNode().insertBefore(fallbackChildren.item(0), xinclude); - } - } else { - LOG(WARNING) << "No xi:fallback found for '" << DOMUtils::xPathForNode(xinclude) << "', document most likely incomplete"; - } - } -REMOVE_AND_RECURSE: - xinclude.getParentNode().removeChild(xinclude); - for (size_t i = 0; i < newNodes.size(); i++) { - _baseURL[newNodes[i]] = URL::asBaseURL(src); - Arabica::XPath::NodeSet<std::string> xincludes = DOMUtils::filterChildElements(xIncludeNS + "include", newNodes[i], true); - for (size_t j = 0; j < xincludes.size(); j++) { - resolveXIncludes(includeChain, mergedNS, xIncludeNS, URL::asBaseURL(src), Element<std::string>(xincludes[j])); - } - } -} - -void InterpreterImpl::init() { - // make sure we have a factory if none was set before - if (_factory == NULL) - _factory = Factory::getInstance(); - - // setup and normalize DOM - setupDOM(); - - // get our name or generate as UUID - if (_name.length() == 0) - _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : UUID::getUUID()); - - // setup event queue for delayed send - if (!_sendQueue) { - _sendQueue = new DelayedEventQueue(); - _sendQueue->start(); - } - - // start io processoes - setupIOProcessors(); - - // instantiate datamodel if not explicitly set - if (!_dataModel) { - if (HAS_ATTR(_scxml, "datamodel")) { - // might throw - _dataModel = _factory->createDataModel(ATTR(_scxml, "datamodel"), this); - for (std::set<DataModelExtension*>::iterator extIter = _dataModelExtensions.begin(); extIter != _dataModelExtensions.end(); extIter++) { - _dataModel.addExtension(*extIter); - } - } else { - _dataModel = _factory->createDataModel("null", this); - } - } - - _dataModel.assign("_x.args", _cmdLineOptions); - -// _running = true; -#if VERBOSE - std::cerr << "running " << this << std::endl; -#endif - - if (_binding == EARLY) { - // initialize all data elements - NodeSet<std::string> dataElems = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "data", _scxml).asNodeSet(); - for (unsigned int i = 0; i < dataElems.size(); i++) { - // do not process data elements of nested documents from invokers - if (!getAncestorElement(dataElems[i], _nsInfo.xmlNSPrefix + "invoke")) - if (dataElems[i].getNodeType() == Node_base::ELEMENT_NODE) { - initializeData(Element<std::string>(dataElems[i])); - } - } - } else { - // initialize current data elements - NodeSet<std::string> topDataElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "data", DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "datamodel", _scxml)); - for (unsigned int i = 0; i < topDataElems.size(); i++) { - if (topDataElems[i].getNodeType() == Node_base::ELEMENT_NODE) - initializeData(Element<std::string>(topDataElems[i])); - } - } - - // executeGlobalScriptElements - NodeSet<std::string> globalScriptElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "script", _scxml); - for (unsigned int i = 0; i < globalScriptElems.size(); i++) { - executeContent(Element<std::string>(globalScriptElems[i])); - } - - _isInitialized = true; - _stable = false; - setInterpreterState(USCXML_INITIALIZED); -} - -/** - * Called with a single data element from the topmost datamodel element. - */ -void InterpreterImpl::initializeData(const Element<std::string>& data) { - - /// test 226/240 - initialize from invoke request - if (_invokeReq.params.find(ATTR(data, "id")) != _invokeReq.params.end()) { - try { - _dataModel.init(ATTR(data, "id"), _invokeReq.params.find(ATTR(data, "id"))->second); - } catch (Event e) { - LOG(ERROR) << "Syntax error when initializing data " << DOMUtils::xPathForNode(data) << " from parameters:" << std::endl << e << std::endl; - receiveInternal(e); - } - return; - } - if (_invokeReq.namelist.find(ATTR(data, "id")) != _invokeReq.namelist.end()) { - try { - _dataModel.init(ATTR(data, "id"), _invokeReq.namelist.find(ATTR(data, "id"))->second); - } catch (Event e) { - LOG(ERROR) << "Syntax error when initializing data " << DOMUtils::xPathForNode(data) << " from namelist:" << std::endl << e << std::endl; - receiveInternal(e); - } - return; - } - - try { - Arabica::DOM::Node<std::string> dom; - std::string text; - processDOMorText(data, dom, text); - _dataModel.init(data, dom, text); - } catch (Event e) { - LOG(ERROR) << "Syntax error when initializing data " << DOMUtils::xPathForNode(data) << ":" << std::endl << e << std::endl; - receiveInternal(e); - } -} - -void InterpreterImpl::receiveInternal(const Event& event) { -#if VERBOSE - std::cerr << _name << " receiveInternal: " << event.name << std::endl; -#endif - _internalQueue.push_back(event); -// _condVar.notify_all(); -} - -void InterpreterImpl::receive(const Event& event, bool toFront) { -#if VERBOSE - std::cerr << _name << " receive: " << event.name << std::endl; -#endif - if (toFront) { - _externalQueue.push_front(event); - } else { - _externalQueue.push(event); - } - _condVar.notify_all(); -} - -void InterpreterImpl::internalDoneSend(const Arabica::DOM::Element<std::string>& state, const Arabica::DOM::Element<std::string>& doneData) { - if (!isState(state)) - return; - -// if (parentIsScxmlState(state)) -// return; - - Event event; - - if (doneData) { - processParamChilds(doneData, event.params); - Arabica::XPath::NodeSet<std::string> contents = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "content", doneData); - if (contents.size() > 1) - LOG(ERROR) << "Only a single content element is allowed for send elements - using first one"; - if (contents.size() > 0) { - std::string expr; - processContentElement(Element<std::string>(contents[0]), event.dom, event.content, expr); - if (expr.length() > 0) { - try { - event.content =_dataModel.evalAsString(expr); - } catch (Event e) { - e.name = "error.execution"; - e.dom = contents[0]; - receiveInternal(e); - } - } - } - } - - event.name = "done.state." + ATTR_CAST(state, "id"); // parent?! - receiveInternal(event); - -} - -void InterpreterImpl::processContentElement(const Arabica::DOM::Element<std::string>& content, - Arabica::DOM::Node<std::string>& dom, - std::string& text, - std::string& expr) { - if (HAS_ATTR(content, "expr")) { - expr = ATTR(content, "expr"); - } else if (content.hasChildNodes() || HAS_ATTR(content, "src") || HAS_ATTR(content, "srcexpr")) { - processDOMorText(content, dom, text); - } else { - LOG(ERROR) << "content element does not specify any content."; - } -} - -void InterpreterImpl::processDOMorText(const Arabica::DOM::Element<std::string>& element, - Arabica::DOM::Node<std::string>& dom, - std::string& text) { - // do we need to download? - if (HAS_ATTR(element, "src") || - (HAS_ATTR(element, "srcexpr"))) { - std::stringstream srcContent; - URL sourceURL(HAS_ATTR(element, "srcexpr") ? _dataModel.evalAsString(ATTR(element, "srcexpr")) : ATTR(element, "src")); - if (!sourceURL.toAbsolute(getBaseURLForNode(element))) { - LOG(ERROR) << LOCALNAME(element) << " element has relative src or srcexpr URL with no baseURL set."; - return; - } - if (_cachedURLs.find(sourceURL.asString()) != _cachedURLs.end() && false) { - srcContent << _cachedURLs[sourceURL.asString()]; - } else { - srcContent << sourceURL; - if (sourceURL.downloadFailed()) { - LOG(ERROR) << LOCALNAME(element) << " source cannot be downloaded"; - return; - } - _cachedURLs[sourceURL.asString()] = sourceURL; - } - if (srcContent.str().length() > 0) { - // try to parse as XML - NameSpacingParser parser; - std::stringstream* ss = new std::stringstream(); - (*ss) << srcContent.str(); - std::auto_ptr<std::istream> ssPtr(ss); - Arabica::SAX::InputSource<std::string> inputSource; - inputSource.setByteStream(ssPtr); - - if (parser.parse(inputSource) && parser.getDocument()) { - Document<std::string> doc = parser.getDocument(); - dom = doc.getDocumentElement(); - return; - } else { - text = srcContent.str(); - return; - } - } - } - - if (!element.hasChildNodes()) - return; - - /** - * Figure out whether the given element contains text, has one child or many childs - */ - bool hasTextContent = false; - bool hasOneChild = false; - Node<std::string> theOneChild; - bool hasManyChilds = false; - - Node<std::string> child = element.getFirstChild(); - while (child) { -// std::cerr << child.getNodeType() << std::endl; - if (child.getNodeType() == Node_base::TEXT_NODE || - child.getNodeType() == Node_base::CDATA_SECTION_NODE) { - std::string trimmed = child.getNodeValue(); - boost::trim(trimmed); - if (trimmed.length() > 0) { - hasTextContent = true; - } - } else { - if (hasOneChild) { - hasManyChilds = true; - hasOneChild = false; - break; - } - hasOneChild = true; - theOneChild = child; - } - child = child.getNextSibling(); - } - - if (hasOneChild) { - // if we have a single child, it will be the content of the dom - dom = theOneChild; - } else if (hasManyChilds) { - // if we have multiple childs - dom = element; - } else if(hasTextContent) { - child = element.getFirstChild(); - while(child) { - if ((child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE)) { - text += child.getNodeValue(); - } - child = child.getNextSibling(); - } - // updated test 179 - conflicts with test 562 -// text = _dataModel.evalAsString(text); - } else { - LOG(ERROR) << LOCALNAME(element) << " has neither text nor element children."; - } -} - -void InterpreterImpl::processParamChilds(const Arabica::DOM::Element<std::string>& element, std::multimap<std::string, Data>& params) { - NodeSet<std::string> paramElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "param", element); - for (size_t i = 0; i < paramElems.size(); i++) { - try { - Element<std::string> paramElem = Element<std::string>(paramElems[i]); - if (!HAS_ATTR(paramElem, "name")) { - LOG(ERROR) << "param element is missing name attribute"; - continue; - } - Data paramValue; - if (HAS_ATTR(paramElem, "expr")) { - paramValue = _dataModel.getStringAsData(ATTR(paramElem, "expr")); - } else if(HAS_ATTR(paramElem, "location")) { - paramValue = _dataModel.getStringAsData(ATTR(paramElem, "location")); - } else { - LOG(ERROR) << "param element is missing expr or location or no datamodel is specified"; - continue; - } - std::string paramKey = ATTR(paramElem, "name"); - params.insert(std::make_pair(paramKey, paramValue)); - } catch(Event e) { - LOG(ERROR) << "Syntax error while processing params " << DOMUtils::xPathForNode(paramElems[i]) << ":" << std::endl << e << std::endl; - // test 343 - std::multimap<std::string, Data>::iterator paramIter = params.begin(); - while(paramIter != params.end()) { - params.erase(paramIter++); - } - e.name = "error.execution"; - receiveInternal(e); - break; - } - } -} - -void InterpreterImpl::send(const Arabica::DOM::Element<std::string>& element) { - SendRequest sendReq; - // test 331 - sendReq.Event::eventType = Event::EXTERNAL; - sendReq.elem = element; - try { - // event - if (HAS_ATTR(element, "eventexpr")) { - sendReq.name = _dataModel.evalAsString(ATTR(element, "eventexpr")); - } else if (HAS_ATTR(element, "event")) { - sendReq.name = ATTR(element, "event"); - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element eventexpr " << DOMUtils::xPathForNode(element) << ":" << std::endl << e << std::endl; - return; - } - try { - // target - if (HAS_ATTR(element, "targetexpr")) { - sendReq.target = _dataModel.evalAsString(ATTR(element, "targetexpr")); - } else if (HAS_ATTR(element, "target")) { - sendReq.target = ATTR(element, "target"); - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " targetexpr:" << std::endl << e << std::endl; - return; - } - try { - // type - if (HAS_ATTR(element, "typeexpr")) { - sendReq.type = _dataModel.evalAsString(ATTR(element, "typeexpr")); - } else if (HAS_ATTR(element, "type")) { - sendReq.type = ATTR(element, "type"); - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " typeexpr:" << std::endl << e << std::endl; - return; - } - try { - // id - if (HAS_ATTR(element, "id")) { - sendReq.sendid = ATTR(element, "id"); - } else { - /* - * The ids for <send> and <invoke> are subtly different. In a conformant - * SCXML document, they must be unique within the session, but in the case - * where the author does not provide them, the processor must generate a - * new unique ID not at load time but each time the element is executed. - * Furthermore the attribute 'idlocation' can be used to capture this - * automatically generated id. Finally note that the automatically generated - * id for <invoke> has a special format. See 6.4.1 Attribute Details for - * details. The SCXML processor may generate all other ids in any format, - * as long as they are unique. - */ - - /** - * - * If 'idlocation' is present, the SCXML Processor must generate an id when - * the parent <send> element is evaluated and store it in this location. - * See 3.14 IDs for details. - * - */ - sendReq.sendid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); - if (HAS_ATTR(element, "idlocation")) { - _dataModel.assign(ATTR(element, "idlocation"), Data(sendReq.sendid, Data::VERBATIM)); - } else { - sendReq.hideSendId = true; - } - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " idlocation:" << std::endl << e << std::endl; - return; - } - - try { - // delay - std::string delay; - sendReq.delayMs = 0; - if (HAS_ATTR(element, "delayexpr")) { - delay = _dataModel.evalAsString(ATTR(element, "delayexpr")); - } else if (HAS_ATTR(element, "delay")) { - delay = ATTR(element, "delay"); - } - if (delay.size() > 0) { - boost::trim(delay); - - NumAttr delayAttr(delay); - if (iequals(delayAttr.unit, "ms")) { - sendReq.delayMs = strTo<uint32_t>(delayAttr.value); - } else if (iequals(delayAttr.unit, "s")) { - sendReq.delayMs = strTo<double>(delayAttr.value) * 1000; - } else if (delayAttr.unit.length() == 0) { // unit less delay is interpreted as milliseconds - sendReq.delayMs = strTo<uint32_t>(delayAttr.value); - } else { - LOG(ERROR) << "Cannot make sense of delay value " << delay << ": does not end in 's' or 'ms'"; - } - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " delayexpr:" << std::endl << e << std::endl; - return; - } - - try { - // namelist - if (HAS_ATTR(element, "namelist")) { - std::list<std::string> names = tokenize(ATTR(element, "namelist")); - for (std::list<std::string>::const_iterator nameIter = names.begin(); nameIter != names.end(); nameIter++) { - if (!_dataModel.isLocation(*nameIter)) { - LOG(ERROR) << "Error in send element " << DOMUtils::xPathForNode(element) << " namelist:" << std::endl << "'" << *nameIter << "' is not a location expression" << std::endl; - ERROR_EXECUTION2(err, "Location expression '" + *nameIter + "' in namelist is invalid", element); - receiveInternal(err); - return; - } - Data namelistValue = _dataModel.getStringAsData(*nameIter); - sendReq.namelist[*nameIter] = namelistValue; - sendReq.data.compound[*nameIter] = namelistValue; // this is questionable - } - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " namelist:" << std::endl << e << std::endl; - return; - } - - try { - // params - processParamChilds(element, sendReq.params); - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " param expr:" << std::endl << e << std::endl; - return; - } - try { - // content - NodeSet<std::string> contents = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "content", element); - if (contents.size() > 1) - LOG(ERROR) << "Only a single content element is allowed for send elements " << DOMUtils::xPathForNode(element) << " - using first one"; - if (contents.size() > 0) { - std::string expr; - processContentElement(Element<std::string>(contents[0]), sendReq.dom, sendReq.content, expr); - if (expr.length() > 0) { - try { - sendReq.data = _dataModel.getStringAsData(expr); - } catch (Event e) { - e.name = "error.execution"; - receiveInternal(e); - return; - } - // set as content if it's only an atom - if (sendReq.data.atom.length() > 0) { - sendReq.content = sendReq.data.atom; - } - } else if (sendReq.content.length() > 0) { - sendReq.data = Data::fromJSON(sendReq.content); - } - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " content:" << std::endl << e << std::endl; - return; - } - - if (sendReq.dom) { - std::stringstream ss; - ss << sendReq.dom; - sendReq.xml = ss.str(); - _dataModel.replaceExpressions(sendReq.xml); - } - - assert(_sendIds.find(sendReq.sendid) == _sendIds.end()); - _sendIds[sendReq.sendid] = std::make_pair(this, sendReq); - if (sendReq.delayMs > 0) { - _sendQueue->addEvent(sendReq.sendid, InterpreterImpl::delayedSend, sendReq.delayMs, &_sendIds[sendReq.sendid]); - } else { - delayedSend(&_sendIds[sendReq.sendid], sendReq.name); - } -} - -void InterpreterImpl::delayedSend(void* userdata, std::string eventName) { - std::pair<InterpreterImpl*, SendRequest>* data = (std::pair<InterpreterImpl*, SendRequest>*)(userdata); - - InterpreterImpl* INSTANCE = data->first; - SendRequest sendReq = data->second; - - /** - * If neither the 'type' nor the 'typeexpr' is defined, the SCXML Processor - * must assume the default value of http://www.w3.org/TR/scxml/#SCXMLEventProcessor - */ - if (sendReq.type.length() == 0) - sendReq.type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; - - IOProcessor ioProc = INSTANCE->getIOProcessor(sendReq.type); - if (ioProc) { - try { - ioProc.send(sendReq); - } catch(Event e) { - throw e; - } catch (const std::exception &e) { - LOG(ERROR) << "Exception caught while sending event to ioprocessor " << sendReq.type << ": '" << e.what() << "'"; - } catch(...) { - LOG(ERROR) << "Exception caught while sending event to ioprocessor " << sendReq.type; - } - } else { - /** - * If the SCXML Processor does not support the type that is specified, it - * must place the event error.execution on the internal event queue. - */ - ERROR_EXECUTION(exc, "Unsupported type '" + sendReq.type + "' with send element"); - INSTANCE->receiveInternal(exc); - } - - assert(INSTANCE->_sendIds.find(sendReq.sendid) != INSTANCE->_sendIds.end()); - INSTANCE->_sendIds.erase(sendReq.sendid); -} - -void InterpreterImpl::invoke(const Arabica::DOM::Element<std::string>& element) { - InvokeRequest invokeReq; - invokeReq.elem = element; - invokeReq.Event::eventType = Event::EXTERNAL; - try { - // type - if (HAS_ATTR(element, "typeexpr")) { - invokeReq.type = _dataModel.evalAsString(ATTR(element, "typeexpr")); - } else if (HAS_ATTR(element, "type")) { - invokeReq.type = ATTR(element, "type"); - } - - // src - std::string source; - if (HAS_ATTR(element, "srcexpr")) { - source = _dataModel.evalAsString(ATTR(element, "srcexpr")); - } else if (HAS_ATTR(element, "src")) { - source = ATTR(element, "src"); - } - if (source.length() > 0) { - URL srcURL(source); - if (!srcURL.toAbsolute(getBaseURLForNode(element))) { - LOG(ERROR) << "invoke element has relative src URL with no baseURL set."; - return; - } - invokeReq.src = srcURL.asString(); - } - - // id - try { - if (HAS_ATTR(element, "id")) { - invokeReq.invokeid = ATTR(element, "id"); - } else { - if (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat")) && HAS_ATTR(element, "parent")) { - invokeReq.invokeid = ATTR(element, "parent") + "." + UUID::getUUID(); - } else { - invokeReq.invokeid = ATTR(getParentState(element), "id") + "." + UUID::getUUID(); - } - if (HAS_ATTR(element, "idlocation")) { - _dataModel.assign(ATTR(element, "idlocation"), Data(invokeReq.invokeid, Data::VERBATIM)); - } - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in invoke element idlocation:" << std::endl << e << std::endl; - return; - } - - // namelist - if (HAS_ATTR(element, "namelist")) { - std::list<std::string> names = tokenize(ATTR(element, "namelist")); - for (std::list<std::string>::const_iterator nameIter = names.begin(); nameIter != names.end(); nameIter++) { - if (!_dataModel.isLocation(*nameIter)) { - LOG(ERROR) << "Error in send element " << DOMUtils::xPathForNode(element) << " namelist:" << std::endl << "'" << *nameIter << "' is not a location expression" << std::endl; - ERROR_EXECUTION2(err, "Location expression '" + *nameIter + "' in namelist is invalid", element); - receiveInternal(err); - return; - } - Data namelistValue = _dataModel.getStringAsData(*nameIter); - invokeReq.namelist[*nameIter] = namelistValue; - invokeReq.data.compound[*nameIter] = namelistValue; - } - } - - // autoforward - if (HAS_ATTR(element, "autoforward")) { - if (iequals(ATTR(element, "autoforward"), "true")) { - invokeReq.autoForward = true; - } - } else { - invokeReq.autoForward = false; - } - - // params - processParamChilds(element, invokeReq.params); - - // content - try { - NodeSet<std::string> contents = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "content", element); - if (contents.size() > 1) - LOG(ERROR) << "Only a single content element is allowed for send elements - using first one"; - if (contents.size() > 0) { - std::string expr; - processContentElement(Element<std::string>(contents[0]), invokeReq.dom, invokeReq.content, expr); - if (expr.length() > 0) { - try { - invokeReq.data =_dataModel.getStringAsData(expr); - } catch (Event e) { - e.name = "error.execution"; - e.dom = contents[0]; - receiveInternal(e); - } - } else if (invokeReq.content.length() > 0) { - invokeReq.data = Data::fromJSON(invokeReq.content); - } - - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(element) << " content:" << std::endl << e << std::endl; - return; - } - - if (invokeReq.dom) { - std::stringstream ss; - ss << invokeReq.dom; - invokeReq.xml = ss.str(); - _dataModel.replaceExpressions(invokeReq.xml); - } - - // test 422 - if (invokeReq.type.size() == 0) - invokeReq.type = "http://www.w3.org/TR/scxml/"; - - Invoker invoker; - // is there such an invoker already? - if (_invokers.find(invokeReq.invokeid) != _invokers.end()) { - invoker = _invokers[invokeReq.invokeid]; - } else { - try { - invoker = _factory->createInvoker(invokeReq.type, this); - invoker.setInvokeId(invokeReq.invokeid); - invoker.setInterpreter(this); - _invokers[invokeReq.invokeid] = invoker; - } catch (Event e) { - receiveInternal(e); - } - } - if (invoker) { - tthread::lock_guard<tthread::recursive_mutex> lock(_pluginMutex); - try { - - if (!invoker.getElement()) - invoker.setElement(Element<std::string>(element)); - - if (invoker.getType().size() == 0) - invoker.setType(invokeReq.type); - - try { - - USCXML_MONITOR_CALLBACK3(beforeInvoking, Arabica::DOM::Element<std::string>(element), invokeReq.invokeid); - - invoker.invoke(invokeReq); - LOG(INFO) << "Added invoker " << invokeReq.type << " at " << invokeReq.invokeid; - - USCXML_MONITOR_CALLBACK3(afterInvoking, Arabica::DOM::Element<std::string>(element), invokeReq.invokeid) - - // this is out of draft but so useful to know when an invoker started - if (HAS_ATTR(element, "callback")) { - std::string callback = ATTR(element, "callback"); - if (callback.size() > 0) { - Event invSuccess; - invSuccess.name = callback + "." + invokeReq.invokeid; - receive(invSuccess); - } - } - - - } catch(boost::bad_lexical_cast e) { - LOG(ERROR) << "Exception caught while sending invoke request to invoker " << invokeReq.invokeid << ": " << e.what(); - } catch (const std::exception &e) { - LOG(ERROR) << "Unknown exception caught while sending invoke request to invoker " << invokeReq.invokeid << ": " << e.what(); - } catch (Event &e) { - LOG(ERROR) << e; - } catch(...) { - LOG(ERROR) << "Unknown exception caught while sending invoke request to invoker " << invokeReq.invokeid; - } - try { -// _dataModel.assign("_invokers['" + invokeReq.invokeid + "']", invoker.getDataModelVariables()); - } catch (const std::exception &e) { - LOG(ERROR) << "Exception caught while assigning datamodel variables from invoker " << invokeReq.invokeid << ": " << e.what(); - } catch(...) { - LOG(ERROR) << "Exception caught while assigning datamodel variables from invoker " << invokeReq.invokeid; - } - } catch (...) { - LOG(ERROR) << "Invoker " << invokeReq.type << " threw an exception"; - } - } else { - LOG(ERROR) << "No invoker known for type " << invokeReq.type; - } - - } catch (Event e) { - LOG(ERROR) << "Syntax error in invoke element " << DOMUtils::xPathForNode(element) << ":" << std::endl << e << std::endl; - } -} - -void InterpreterImpl::cancelInvoke(const Arabica::DOM::Element<std::string>& element) { - std::string invokeId; - if (HAS_ATTR(element, "idlocation")) { - invokeId = _dataModel.evalAsString(ATTR(element, "idlocation")); - } else if (HAS_ATTR(element, "id")) { - invokeId = ATTR(element, "id"); - } else { - assert(false); - } - if (_invokers.find(invokeId) != _invokers.end()) { - LOG(INFO) << "Removed invoker at " << invokeId; - try { - // TODO: this is datamodel specific! - //_dataModel.assign("_invokers['" + invokeId + "']", Data(std::string("''"), Data::INTERPRETED)); - } catch (Event e) { - LOG(ERROR) << "Syntax when removing invoker:" << std::endl << e << std::endl; - } - - USCXML_MONITOR_CALLBACK3(beforeUninvoking, Element<std::string>(element), invokeId) - _invokers[invokeId].uninvoke(); - - /** - * This should not be necessary. Most invokers have their "clean-up" code in their - * destructor and not their uninvoke method - we need to refactor them all! - */ - if (_invokers[invokeId].deleteOnUninvoke()) - _invokers.erase(invokeId); - - USCXML_MONITOR_CALLBACK3(beforeUninvoking, Element<std::string>(element), invokeId) - - } else { - LOG(ERROR) << "Cannot cancel invoke for id " << invokeId << ": no such invokation"; - } - //receiveInternal(Event("done.invoke." + invokeId, Event::PLATFORM)); -} - -bool InterpreterImpl::hasConditionMatch(const Arabica::DOM::Element<std::string>& conditional) { - if (HAS_ATTR(conditional, "cond") && ATTR(conditional, "cond").length() > 0) { - try { - return _dataModel.evalAsBool(conditional, ATTR(conditional, "cond")); - } catch (Event e) { - LOG(ERROR) << "Syntax error in cond attribute of " << TAGNAME(conditional) << " element " << DOMUtils::xPathForNode(conditional) << ":" << std::endl << e << std::endl; - e.name = "error.execution"; - receiveInternal(e); - return false; - } - } - return true; // no condition is always true -} - - -void InterpreterImpl::executeContent(const NodeList<std::string>& content, bool rethrow) { - for (unsigned int i = 0; i < content.getLength(); i++) { - const Arabica::DOM::Node<std::string>& node = content.item(i); - if (node.getNodeType() != Node_base::ELEMENT_NODE) - continue; - try { - executeContent(Element<std::string>(node), true); - } - CATCH_AND_DISTRIBUTE2("Error when executing content", content.item(i)); - } -} - -void InterpreterImpl::executeContent(const NodeSet<std::string>& content, bool rethrow) { - for (unsigned int i = 0; i < content.size(); i++) { - const Arabica::DOM::Node<std::string>& node = content[i]; - if (node.getNodeType() != Node_base::ELEMENT_NODE) - continue; - try { - executeContent(Element<std::string>(node), true); - } - CATCH_AND_DISTRIBUTE2("Error when executing content", content[i]); - } -} - -void InterpreterImpl::executeContent(const Arabica::DOM::Element<std::string>& content, bool rethrow) { - - if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "onentry") || - iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "onexit") || - iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "finalize") || - iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "transition")) { - // --- CONVENIENCE LOOP -------------------------- - NodeList<std::string> executable = content.getChildNodes(); - for (size_t i = 0; i < executable.getLength(); i++) { - const Arabica::DOM::Node<std::string>& childNode = executable.item(i); - if (childNode.getNodeType() != Node_base::ELEMENT_NODE) - continue; - executeContent(Element<std::string>(childNode), rethrow); - } - return; - } - - USCXML_MONITOR_CALLBACK2(beforeExecutingContent, Element<std::string>(content)) - - if (false) { - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "raise")) { - // --- RAISE -------------------------- -#if 0 - if (HAS_ATTR(content, "event")) { - receiveInternal(Event(ATTR(content, "event"))); - } -#else - // Extension for donedata in flat documents - we need to send them internally - use raise - if (HAS_ATTR(content, "event")) { - Event raised(ATTR(content, "event")); - try { - // content - processParamChilds(content, raised.params); - NodeSet<std::string> contents = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "content", content); - if (contents.size() > 1) - LOG(ERROR) << "Only a single content element is allowed for raise elements " << DOMUtils::xPathForNode(content) << " - using first one"; - if (contents.size() > 0) { - std::string expr; - processContentElement(Element<std::string>(contents[0]), raised.dom, raised.content, expr); - if (expr.length() > 0) { - try { - raised.data = _dataModel.getStringAsData(expr); - } catch (Event e) { - e.name = "error.execution"; - receiveInternal(e); - } - // set as content if it's only an atom - if (raised.data.atom.length() > 0) { - raised.content = raised.data.atom; - } - } else if (raised.content.length() > 0) { - raised.data = Data::fromJSON(raised.content); - } - } - } catch (Event e) { - LOG(ERROR) << "Syntax error in send element " << DOMUtils::xPathForNode(content) << " content:" << std::endl << raised << std::endl; - return; - } - - if (raised.dom) { - std::stringstream ss; - ss << raised.dom; - raised.xml = ss.str(); - _dataModel.replaceExpressions(raised.xml); - } - receiveInternal(raised); - } -#endif - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "if")) { - // --- IF / ELSEIF / ELSE -------------- - Arabica::DOM::Element<std::string> ifElem = (Arabica::DOM::Element<std::string>)content; -#if VERBOSE - if (HAS_ATTR(ifElem, "cond")) - std::cerr << ATTR(ifElem, "cond") << std::endl; -#endif - /** - * A block is everything up to or between elseifs and else. Those element - * determine whether the block is true and its executable content executed. - */ - if (ifElem.hasChildNodes()) { - bool blockIsTrue = hasConditionMatch(ifElem); - NodeList<std::string> childs = ifElem.getChildNodes(); - for (unsigned int i = 0; i < childs.getLength(); i++) { - if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) - continue; - Element<std::string> childElem(childs.item(i)); - if (iequals(TAGNAME_CAST(childs.item(i)), _nsInfo.xmlNSPrefix + "elseif") || - iequals(TAGNAME_CAST(childs.item(i)), _nsInfo.xmlNSPrefix + "else")) { - if (blockIsTrue) { - // last block was true, break here - break; - } else { - // is this block the one to execute? - blockIsTrue = hasConditionMatch(childElem); - } - } else { - if (blockIsTrue) { - executeContent(childElem, rethrow); - } - } - } - } - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "elseif")) { - LOG(ERROR) << "Found single elseif to evaluate!" << std::endl; - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "else")) { - LOG(ERROR) << "Found single else to evaluate!" << std::endl; - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "foreach")) { - // --- FOREACH -------------------------- - if (HAS_ATTR(content, "array") && HAS_ATTR(content, "item")) { - std::string array = ATTR(content, "array"); - std::string item = ATTR(content, "item"); - std::string index = (HAS_ATTR(content, "index") ? ATTR(content, "index") : ""); - uint32_t iterations = 0; - try { - iterations = _dataModel.getLength(array); - } - CATCH_AND_DISTRIBUTE2("Syntax error in array attribute of foreach element", content) - try { - _dataModel.pushContext(); // copy old and enter new context -// if (!_dataModel.isDeclared(item)) { -// _dataModel.init(item, Data()); -// } - for (uint32_t iteration = 0; iteration < iterations; iteration++) { - _dataModel.setForeach(item, array, index, iteration); - if (content.hasChildNodes()) - // execute content and have exception rethrown to break foreach - executeContent(content.getChildNodes(), rethrow); - } - _dataModel.popContext(); // leave stacked context - } - CATCH_AND_DISTRIBUTE2("Syntax error in foreach element", content) - } else { - LOG(ERROR) << "Expected array and item attributes with foreach element!" << std::endl; - } - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "log")) { - // --- LOG -------------------------- - Arabica::DOM::Element<std::string> logElem = (Arabica::DOM::Element<std::string>)content; - if (logElem.hasAttribute("label")) { - std::cout << logElem.getAttribute("label") << ": "; - } - if (logElem.hasAttribute("expr")) { - try { - std::string msg = _dataModel.evalAsString(logElem.getAttribute("expr")); - std::cout << msg << std::endl; - } - CATCH_AND_DISTRIBUTE2("Syntax error in expr attribute of log element", content) - } else { - if (logElem.hasAttribute("label")) { - std::cout << std::endl; - } - } - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "assign")) { - // --- ASSIGN -------------------------- - if (HAS_ATTR(content, "location")) { - try { - if (!_dataModel.isDeclared(ATTR(content, "location"))) { - // test 286, 331 - ERROR_EXECUTION_THROW("Assigning to undeclared location '" + ATTR(content, "location") + "' not allowed."); - } else { - Node<std::string> dom; - std::string text; - processDOMorText(content, dom, text); - _dataModel.assign(Element<std::string>(content), dom, text); - } - } - CATCH_AND_DISTRIBUTE2("Syntax error in attributes of assign element", content) - } - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "validate")) { - // --- VALIDATE -------------------------- - std::string location = (HAS_ATTR(content, "location") ? ATTR(content, "location") : ""); - std::string schema = (HAS_ATTR(content, "schema") ? ATTR(content, "schema") : ""); - _dataModel.validate(location, schema); - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "script")) { - // --- SCRIPT -------------------------- - if (HAS_ATTR(content, "src")) { - URL scriptUrl(ATTR(content, "src")); - - if (!scriptUrl.toAbsolute(getBaseURLForNode(content))) { - LOG(ERROR) << "Failed to convert relative script URL " << ATTR(content, "src") << " to absolute with base URL " << getBaseURLForNode(content); - return; - } - - std::stringstream srcContent; - try { - if (_cachedURLs.find(scriptUrl.asString()) != _cachedURLs.end() && false) { - srcContent << _cachedURLs[scriptUrl.asString()]; - } else { - srcContent << scriptUrl; - if (scriptUrl.downloadFailed()) { - LOG(ERROR) << "script element source cannot be downloaded"; - } - _cachedURLs[scriptUrl.asString()] = scriptUrl; - } - } catch (Event exception) { - // script failed to download - if (exception.name == "error.communication") { - throw exception; // terminate test329, test301 - } - receive(exception); - return; - } - - try { - _dataModel.eval((Element<std::string>)content, srcContent.str()); - } - CATCH_AND_DISTRIBUTE("Syntax error while executing script element from '" + ATTR(content, "src") + "':") - } else { - if (content.hasChildNodes()) { - // search for the text node with the actual script - std::string scriptContent; - for (Node<std::string> child = content.getFirstChild(); child; child = child.getNextSibling()) { - if (child.getNodeType() == Node_base::TEXT_NODE || child.getNodeType() == Node_base::CDATA_SECTION_NODE) - scriptContent += child.getNodeValue(); - } - if (scriptContent.size() > 0) { - try { - _dataModel.eval((Element<std::string>)content, scriptContent); - } - CATCH_AND_DISTRIBUTE2("Syntax error while executing script element", content) - } - } - } - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "send")) { - // --- SEND -------------------------- - try { - send(content); - } - CATCH_AND_DISTRIBUTE2("Error while sending content", content) - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "cancel")) { - // --- CANCEL -------------------------- - std::string sendId; - try { - if (HAS_ATTR(content, "sendidexpr")) { - sendId = _dataModel.evalAsString(ATTR(content, "sendidexpr")); - } else if(HAS_ATTR(content, "sendid")) { - sendId = ATTR(content, "sendid"); - } else { - LOG(ERROR) << "Expected sendidexpr or sendid attribute in cancel element"; - return; - } - _sendQueue->cancelEvent(sendId); - { - tthread::lock_guard<tthread::recursive_mutex> lock(_sendQueue->_mutex); - _sendIds.erase(sendId); // issue 68 - } - - } - CATCH_AND_DISTRIBUTE2("Syntax error while executing cancel element", content) - } else if (iequals(TAGNAME(content), _nsInfo.xmlNSPrefix + "invoke")) { - // --- INVOKE -------------------------- - } else { - // --- Custom Executable Content - ExecutableContent execContent; - if (_executableContent.find(content) == _executableContent.end()) { - _executableContent[content] = _factory->createExecutableContent(content.getLocalName(), content.getNamespaceURI(), this); - } - execContent = _executableContent[content]; - - execContent.enterElement(content); - if (execContent.processChildren()) { - NodeList<std::string> executable = content.getChildNodes(); - for (size_t i = 0; i < executable.getLength(); i++) { - if (executable.item(i).getNodeType() != Node_base::ELEMENT_NODE) - continue; - executeContent(Element<std::string>(executable.item(i)), rethrow); - } - } - execContent.exitElement(content); - } - - USCXML_MONITOR_CALLBACK2(afterExecutingContent, Element<std::string>(content)) - -} - -void InterpreterImpl::finalizeAndAutoForwardCurrentEvent() { - for (std::map<std::string, Invoker>::iterator invokeIter = _invokers.begin(); - invokeIter != _invokers.end(); - invokeIter++) { - if (iequals(invokeIter->first, _currEvent.invokeid)) { - Arabica::XPath::NodeSet<std::string> finalizes = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "finalize", invokeIter->second.getElement()); - for (size_t k = 0; k < finalizes.size(); k++) { - Element<std::string> finalizeElem = Element<std::string>(finalizes[k]); - - bool hasChildElems = false; - Node<std::string> child = finalizeElem.getFirstChild(); - while(child) { - if (child.getNodeType() == Node_base::ELEMENT_NODE) { - hasChildElems = true; - break; - } - child = child.getNextSibling(); - } - - if (hasChildElems) { - executeContent(finalizeElem); - } else { - // Specification 6.5.2: http://www.w3.org/TR/scxml/#N110EF - if (HAS_ATTR(invokeIter->second.getElement(), "namelist")) { - std::list<std::string> names = tokenize(ATTR(invokeIter->second.getElement(), "namelist")); - for (std::list<std::string>::iterator nameIter = names.begin(); nameIter != names.end(); nameIter++) { - if (_currEvent.data.compound.find(*nameIter) != _currEvent.data.compound.end()) { - _dataModel.assign(*nameIter, _currEvent.data.compound[*nameIter]); - } - } - } - - Arabica::XPath::NodeSet<std::string> params = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "param", invokeIter->second.getElement()); - for (size_t l = 0; l < params.size(); l++) { - Element<std::string> paramElem = Element<std::string>(params[l]); - if (HAS_ATTR(paramElem, "location") && - _currEvent.data.compound.find(ATTR(paramElem, "location")) != _currEvent.data.compound.end()) { - std::string location = ATTR(paramElem, "location"); - _dataModel.assign(location, _currEvent.data.compound[location]); - } - } - - - } - } - } - if (HAS_ATTR(invokeIter->second.getElement(), "autoforward") && stringIsTrue(ATTR(invokeIter->second.getElement(), "autoforward"))) { - try { - // do not autoforward to invokers that send to #_parent from the SCXML IO Processor! - // Yes do so, see test229! - // if (!boost::equals(_currEvent.getOriginType(), "http://www.w3.org/TR/scxml/#SCXMLEventProcessor")) -// LOG(ERROR) << _sessionId << " auto forwards " << _currEvent.name << " to " << invokeIter->first << std::endl; - invokeIter->second.send(_currEvent); - } catch (const std::exception &e) { - LOG(ERROR) << "Exception caught while sending event to invoker " << invokeIter->first << ": " << e.what(); - } catch(...) { - LOG(ERROR) << "Exception caught while sending event to invoker " << invokeIter->first; - } - } - } -} - -void InterpreterImpl::returnDoneEvent(const Arabica::DOM::Node<std::string>& state) { -// std::cerr << state << std::endl; - if (_parentQueue != NULL) { - Event done; - done.name = "done.invoke." + _sessionId; - _parentQueue->push(done); - } -} - -bool InterpreterImpl::parentIsScxmlState(const Arabica::DOM::Element<std::string>& state) { - Arabica::DOM::Element<std::string> parentElem = (Arabica::DOM::Element<std::string>)state.getParentNode(); - if (iequals(TAGNAME(parentElem), _nsInfo.xmlNSPrefix + "scxml")) - return true; - return false; -} - -bool InterpreterImpl::isInFinalState(const Arabica::DOM::Element<std::string>& state) { - if (isCompound(state)) { - Arabica::XPath::NodeSet<std::string> childs = getChildStates(state); - for (size_t i = 0; i < childs.size(); i++) { - if (isFinal(Element<std::string>(childs[i])) && isMember(childs[i], _configuration)) - return true; - } - } else if (isParallel(state)) { - Arabica::XPath::NodeSet<std::string> childs = getChildStates(state); - for (size_t i = 0; i < childs.size(); i++) { - if (!isInFinalState(Element<std::string>(childs[i]))) - return false; - } - return true; - } - return false; -} - -bool InterpreterImpl::isMember(const Arabica::DOM::Node<std::string>& node, const Arabica::XPath::NodeSet<std::string>& set) { - for (size_t i = 0; i < set.size(); i++) { - if (set[i] == node) - return true; - } - return false; -} - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::getChildStates(const Arabica::DOM::Node<std::string>& state) { - Arabica::XPath::NodeSet<std::string> childs; - - Arabica::DOM::NodeList<std::string> childElems = state.getChildNodes(); - for (size_t i = 0; i < childElems.getLength(); i++) { - if (childElems.item(i).getNodeType() != Node_base::ELEMENT_NODE) - continue; - if (isState(Element<std::string>(childElems.item(i)))) { - childs.push_back(childElems.item(i)); - } - } - return childs; -} - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::getChildStates(const Arabica::XPath::NodeSet<std::string>& states) { - Arabica::XPath::NodeSet<std::string> childs; - for (size_t i = 0; i < states.size(); i++) { - childs.push_back(getChildStates(states[i])); - } - return childs; -} - -Arabica::DOM::Element<std::string> InterpreterImpl::getParentState(const Arabica::DOM::Node<std::string>& element) { - Arabica::DOM::Node<std::string> parent = element.getParentNode(); - while(parent && !isState(Element<std::string>(parent))) { - parent = parent.getParentNode(); - } - return Element<std::string>(parent); -} - -Arabica::DOM::Node<std::string> InterpreterImpl::getAncestorElement(const Arabica::DOM::Node<std::string>& node, const std::string tagName) { - Arabica::DOM::Node<std::string> parent = node.getParentNode(); - while(parent) { - if (parent.getNodeType() == Node_base::ELEMENT_NODE && - iequals(TAGNAME_CAST(parent), tagName)) { - return parent; - } - parent = parent.getParentNode(); - } - return Arabica::DOM::Node<std::string>(); -} - -/** - See: http://www.w3.org/TR/scxml/#LCCA - The Least Common Compound Ancestor is the <state> or <scxml> element s such that s is a proper ancestor - of all states on stateList and no descendant of s has this property. Note that there is guaranteed to be - such an element since the <scxml> wrapper element is a common ancestor of all states. Note also that since - we are speaking of proper ancestor (parent or parent of a parent, etc.) the LCCA is never a member of stateList. -*/ - -#define VERBOSE_FIND_LCCA 0 -Arabica::DOM::Node<std::string> InterpreterImpl::findLCCA(const Arabica::XPath::NodeSet<std::string>& states) { -#if VERBOSE_FIND_LCCA - std::cerr << "findLCCA: "; - for (size_t i = 0; i < states.size(); i++) { - std::cerr << ATTR_CAST(states[i], "id") << ", "; - } - std::cerr << std::endl << std::flush; -#endif - - Arabica::XPath::NodeSet<std::string> ancestors = getProperAncestors(states[0], Arabica::DOM::Node<std::string>()); - Arabica::DOM::Node<std::string> ancestor; - - for (size_t i = 0; i < ancestors.size(); i++) { - if (!isCompound(Element<std::string>(ancestors[i]))) - continue; - for (size_t j = 0; j < states.size(); j++) { - -#if VERBOSE_FIND_LCCA - std::cerr << "Checking " << ATTR_CAST(states[j], "id") << " and " << ATTR_CAST(ancestors[i], "id") << std::endl; -#endif - - if (!isDescendant(states[j], ancestors[i])) - goto NEXT_ANCESTOR; - } - ancestor = ancestors[i]; - break; -NEXT_ANCESTOR: - ; - } - - // take uppermost root as ancestor - if (!ancestor) - ancestor = _scxml; - -#if VERBOSE_FIND_LCCA - std::cerr << " -> " << ATTR_CAST(ancestor, "id") << " " << ancestor.getLocalName() << std::endl; -#endif - return ancestor; -} - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::getStates(const std::list<std::string>& stateIds) { - Arabica::XPath::NodeSet<std::string> states; - std::list<std::string>::const_iterator tokenIter = stateIds.begin(); - while(tokenIter != stateIds.end()) { - states.push_back(getState(*tokenIter)); - tokenIter++; - } - return states; -} - -Arabica::DOM::Element<std::string> InterpreterImpl::getState(const std::string& stateId) { - - if (_cachedStates.find(stateId) != _cachedStates.end()) { - return _cachedStates[stateId]; - } - -#if 0 - std::list<Element<std::string> > stateStack; - stateStack.push_back(_scxml); - - - while(stateStack.size() > 0) { - Element<std::string> curr = stateStack.front(); - stateStack.pop_front(); - - if (HAS_ATTR(curr, "id") && ATTR(curr, "id") == stateId) { - _cachedStates[stateId] = curr; - return curr; - } - - Arabica::XPath::NodeSet<std::string> children = getChildStates(curr); - stateStack.insert(stateStack.end(), children.begin(), children.end()); - } - -#else - // first try atomic and compound states - NodeSet<std::string> target = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "state[@id='" + stateId + "']", _scxml).asNodeSet(); - if (target.size() > 0) - goto FOUND; - - // now parallel states - target = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "parallel[@id='" + stateId + "']", _scxml).asNodeSet(); - if (target.size() > 0) - goto FOUND; - - // now final states - target = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "final[@id='" + stateId + "']", _scxml).asNodeSet(); - if (target.size() > 0) - goto FOUND; - - // now history states - target = _xpath.evaluate("//" + _nsInfo.xpathPrefix + "history[@id='" + stateId + "']", _scxml).asNodeSet(); - if (target.size() > 0) - goto FOUND; - { - ERROR_EXECUTION_THROW("No state with id '" + stateId + "' found"); - } -FOUND: - if (target.size() > 0) { - for (size_t i = 0; i < target.size(); i++) { - Element<std::string> targetElem(target[i]); - if (!isInEmbeddedDocument(targetElem)) { - _cachedStates[stateId] = targetElem; - return targetElem; - } - } - } -#endif - - // return the empty node - return Arabica::DOM::Element<std::string>(); -} - -Arabica::XPath::NodeSet<std::string> InterpreterImpl::getAllStates() { - NodeSet<std::string> states; - states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "state", _scxml).asNodeSet()); - states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "parallel", _scxml).asNodeSet()); - states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "final", _scxml).asNodeSet()); - states.push_back(_xpath.evaluate("//" + _nsInfo.xpathPrefix + "history", _scxml).asNodeSet()); - return states; -} - -Arabica::DOM::Node<std::string> InterpreterImpl::getSourceState(const Arabica::DOM::Element<std::string>& transition) { - if (iequals(TAGNAME_CAST(transition.getParentNode()), _nsInfo.xmlNSPrefix + "initial")) - return transition.getParentNode().getParentNode(); - return transition.getParentNode(); -} - -/** - * In a conformant SCXML document, a compound state may specify either an "initial" - * attribute or an <initial> element, but not both. See 3.6 <initial> for a - * discussion of the difference between the two notations. If neither the "initial" - * attribute nor an <initial> element is specified, the SCXML Processor must use - * the first child state in document order as the default initial state. - */ -Arabica::XPath::NodeSet<std::string> InterpreterImpl::getInitialStates(Arabica::DOM::Element<std::string> state) { - if (!state) { - state = _scxml; - } - -#if VERBOSE - std::cerr << "Getting initial state of " << TAGNAME(state) << " " << ATTR(state, "id") << std::endl; -#endif - - if (isAtomic(state)) { - return Arabica::XPath::NodeSet<std::string>(); - } - - assert(isCompound(state) || isParallel(state)); - - if (isParallel(state)) { - return getChildStates(state); - } - - // initial attribute at element - Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)state; - if (stateElem.hasAttribute("initial")) { - return getStates(tokenize(stateElem.getAttribute("initial"))); - } - - // initial element as child - but not the implicit generated one - NodeSet<std::string> initElems = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "initial", state); - if(initElems.size() > 0 && !iequals(ATTR_CAST(initElems[0], "generated"), "true")) { - NodeSet<std::string> initTrans = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", initElems[0]); - if (initTrans.size() > 0) { - return getTargetStates(Element<std::string>(initTrans[0])); - } - } - - // first child state - Arabica::XPath::NodeSet<std::string> initStates; - NodeList<std::string> childs = state.getChildNodes(); - for (size_t i = 0; i < childs.getLength(); i++) { - if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) - continue; - if (isState(Element<std::string>(childs.item(i)))) { - initStates.push_back(childs.item(i)); - return initStates; - } - - } - // nothing found - return Arabica::XPath::NodeSet<std::string>(); -} - -NodeSet<std::string> InterpreterImpl::getReachableStates() { - /** Check which states are reachable */ - - NodeSet<std::string> reachable; - reachable.push_back(_scxml); - - bool hasChanges = true; - - while (hasChanges) { - // iterate initials and transitions until stable, unneccerily iterates complete reachable set everytime - - hasChanges = false; - // reachable per initial attribute or document order - size will increase as we append new states - for (size_t i = 0; i < reachable.size(); i++) { - // get the state's initial states - Element<std::string> state = Element<std::string>(reachable[i]); - try { - NodeSet<std::string> initials = getInitialStates(state); - for (size_t j = 0; j < initials.size(); j++) { - Element<std::string> initial = Element<std::string>(initials[j]); - if (!InterpreterImpl::isMember(initial, reachable)) { - reachable.push_back(initial); - hasChanges = true; - } - } - } catch (Event e) { - } - } - - // reachable per target attribute in transitions - for (size_t i = 0; i < reachable.size(); i++) { - Element<std::string> state = Element<std::string>(reachable[i]); - NodeSet<std::string> transitions = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "transition", state, false); - for (size_t j = 0; j < transitions.size(); j++) { - Element<std::string> transition = Element<std::string>(transitions[j]); - try { - NodeSet<std::string> targets = getTargetStates(transition); - for (size_t k = 0; k < targets.size(); k++) { - Element<std::string> target = Element<std::string>(targets[k]); - if (!InterpreterImpl::isMember(target, reachable)) { - reachable.push_back(target); - hasChanges = true; - } - } - } catch (Event e) { - } - } - } - - // reachable via a reachable child state - for (size_t i = 0; i < reachable.size(); i++) { - Element<std::string> state = Element<std::string>(reachable[i]); - if (InterpreterImpl::isAtomic(state)) { - // iterate the states parents - Node<std::string> parent = state.getParentNode(); - while(parent && parent.getNodeType() == Node_base::ELEMENT_NODE) { - Element<std::string> parentElem = Element<std::string>(parent); - if (!InterpreterImpl::isState(parentElem)) { - break; - } - if (!InterpreterImpl::isMember(parentElem, reachable)) { - reachable.push_back(parent); - hasChanges = true; - } - parent = parent.getParentNode(); - } - } - } - } - - - return reachable; -} - - -NodeSet<std::string> InterpreterImpl::getTargetStates(const Arabica::DOM::Element<std::string>& transition) { - if (_cachedTargets.find(transition) != _cachedTargets.end()) - return _cachedTargets[transition]; - - NodeSet<std::string> targetStates; - - assert(boost::ends_with(TAGNAME(transition), "transition")); - - // if we are called with a state, process all its transitions - if (isState(transition) || (transition.getNodeType() == Node_base::ELEMENT_NODE && iequals(_nsInfo.xmlNSPrefix + "initial", TAGNAME(transition)))) { - NodeList<std::string> childs = transition.getChildNodes(); - for (size_t i = 0; i < childs.getLength(); i++) { - if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) - continue; - Element<std::string> childElem = Element<std::string>(childs.item(i)); - if (iequals(TAGNAME(childElem), _nsInfo.xmlNSPrefix + "transition")) { - targetStates.push_back(getTargetStates(childElem)); - } - } - _cachedTargets[transition] = targetStates; - return targetStates; - } - - std::string targetId = ((Arabica::DOM::Element<std::string>)transition).getAttribute("target"); - std::list<std::string> targetIds = tokenize(ATTR(transition, "target")); - for (std::list<std::string>::const_iterator targetIter = targetIds.begin(); targetIter != targetIds.end(); targetIter++) { - Arabica::DOM::Node<std::string> state = getState(*targetIter); - if (state) { - assert(HAS_ATTR_CAST(state, "id")); - targetStates.push_back(state); - } - } - _cachedTargets[transition] = targetStates; - return targetStates; -} - -NodeSet<std::string> InterpreterImpl::getTargetStates(const Arabica::XPath::NodeSet<std::string>& transitions) { - NodeSet<std::string> targets; - for (unsigned int i = 0; i < transitions.size(); i++) { - targets.push_back(getTargetStates(Element<std::string>(transitions[i]))); - } - return targets; -} - - -/* - * If state2 is null, returns the set of all ancestors of state1 in ancestry order - * (state1's parent followed by the parent's parent, etc. up to an including the <scxml> - * element). If state2 is non-null, returns in ancestry order the set of all ancestors - * of state1, up to but not including state2. (A "proper ancestor" of a state is its - * parent, or the parent's parent, or the parent's parent's parent, etc.))If state2 is - * state1's parent, or equal to state1, or a descendant of state1, this returns the empty set. - */ - -NodeSet<std::string> InterpreterImpl::getProperAncestors(const Arabica::DOM::Node<std::string>& s1, - const Arabica::DOM::Node<std::string>& s2) { - - std::pair<Arabica::DOM::Node<std::string>, Arabica::DOM::Node<std::string> > ancPair = std::make_pair(s1, s2); - if (_cachedProperAncestors.find(ancPair) != _cachedProperAncestors.end()) - return _cachedProperAncestors[ancPair]; - - NodeSet<std::string> ancestors; - if (isState(Element<std::string>(s1))) { - Arabica::DOM::Node<std::string> node = s1; - while((node = node.getParentNode())) { - if (node.getNodeType() != Node_base::ELEMENT_NODE) - break; - - Element<std::string> nodeElem(node); - if (!isState(nodeElem)) - break; -// if (iequals(TAGNAME(nodeElem), _nsInfo.xmlNSPrefix + "scxml")) // do not return scxml root itself - this is somewhat ill-defined -// break; - if (!iequals(TAGNAME(nodeElem), _nsInfo.xmlNSPrefix + "parallel") && - !iequals(TAGNAME(nodeElem), _nsInfo.xmlNSPrefix + "state") && - !iequals(TAGNAME(nodeElem), _nsInfo.xmlNSPrefix + "scxml")) - break; - if (node == s2) - break; - ancestors.push_back(node); - } - } - _cachedProperAncestors[ancPair] = ancestors; - return ancestors; -} - -bool InterpreterImpl::isDescendant(const Arabica::DOM::Node<std::string>& s1, - const Arabica::DOM::Node<std::string>& s2) { - if (!s1 || !s2) - return false; - - Arabica::DOM::Node<std::string> parent = s1.getParentNode(); - while(parent) { - if (s2 == parent) - return true; - parent = parent.getParentNode(); - } - return false; -} - -bool InterpreterImpl::isTargetless(const Arabica::DOM::Element<std::string>& transition) { - if (transition.hasAttribute("target")) { - return false; - } - return true; -} - -bool InterpreterImpl::isState(const Arabica::DOM::Element<std::string>& state) { - if (!state) - return false; - - std::string tagName = LOCALNAME(state); - if (iequals("state", tagName)) - return true; - if (iequals("scxml", tagName)) - return true; - if (iequals("parallel", tagName)) - return true; -// if (iequals("history", tagName)) // this is no state, see mail to W3C list -// return true; - if (iequals("final", tagName)) - return true; - return false; -} - -bool InterpreterImpl::isFinal(const Arabica::DOM::Element<std::string>& state) { - std::string tagName = LOCALNAME(state); - if (iequals("final", tagName)) - return true; - if (HAS_ATTR(state, "final") && iequals("true", ATTR(state, "final"))) - return true; - return false; -} - -bool InterpreterImpl::isInEmbeddedDocument(const Node<std::string>& node) { - // a node is in an embedded document if there is a content element in its parents - Node<std::string> parent = node; - while(parent) { - if(iequals(parent.getLocalName(), "content")) { - return true; - } - parent = parent.getParentNode(); - } - return false; -} - -bool InterpreterImpl::isInitial(const Arabica::DOM::Element<std::string>& state) { - if (!isState(state)) - return false; - - Arabica::DOM::Node<std::string> parentNode = state.getParentNode(); - if (parentNode.getNodeType() != Node_base::ELEMENT_NODE) - return false; - - Arabica::DOM::Element<std::string> parent = (Element<std::string>)parentNode; - if (!isState(parent)) - return true; // scxml element - - if (isMember(state, getInitialStates(parent))) - return true; // every nested node - -// if (isParallel(parent)) -// return true; - - return false; -} - -bool InterpreterImpl::isPseudoState(const Arabica::DOM::Element<std::string>& state) { - std::string tagName = LOCALNAME(state); - if (iequals("initial", tagName)) - return true; - if (iequals("history", tagName)) - return true; - return false; -} - -bool InterpreterImpl::isTransitionTarget(const Arabica::DOM::Element<std::string>& elem) { - return (isState(elem) || iequals(LOCALNAME(elem), "history")); -} - -bool InterpreterImpl::isAtomic(const Arabica::DOM::Element<std::string>& state) { - if (iequals("final", LOCALNAME(state))) - return true; - - if (iequals("parallel", LOCALNAME(state))) - return false; - - Arabica::DOM::NodeList<std::string> childs = state.getChildNodes(); - for (unsigned int i = 0; i < childs.getLength(); i++) { - if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) - continue; - - if (isState(Element<std::string>(childs.item(i)))) - return false; - } - return true; -} - -bool InterpreterImpl::isHistory(const Arabica::DOM::Element<std::string>& state) { - if (iequals("history", LOCALNAME(state))) - return true; - return false; -} - -bool InterpreterImpl::isParallel(const Arabica::DOM::Element<std::string>& state) { - if (!isState(state)) - return false; - if (iequals("parallel", LOCALNAME(state))) - return true; - return false; -} - - -bool InterpreterImpl::isCompound(const Arabica::DOM::Element<std::string>& state) { - if (!isState(state)) - return false; - - if (iequals(LOCALNAME(state), "parallel")) // parallel is no compound state - return false; - - Arabica::DOM::NodeList<std::string> childs = state.getChildNodes(); - for (unsigned int i = 0; i < childs.getLength(); i++) { - if (childs.item(i).getNodeType() != Node_base::ELEMENT_NODE) - continue; - if (isState(Element<std::string>(childs.item(i)))) - return true; - } - return false; -} - -void InterpreterImpl::setupIOProcessors() { - tthread::lock_guard<tthread::recursive_mutex> lock(_pluginMutex); - std::map<std::string, IOProcessorImpl*> ioProcs = _factory->getIOProcessors(); - std::map<std::string, IOProcessorImpl*>::iterator ioProcIter = ioProcs.begin(); - while(ioProcIter != ioProcs.end()) { - if (iequals(ioProcIter->first, "basichttp") && !(_capabilities & CAN_BASIC_HTTP)) { - ioProcIter++; - continue; - } - if (iequals(ioProcIter->first, "http") && !(_capabilities & CAN_GENERIC_HTTP)) { - ioProcIter++; - continue; - } - - // do not override if already set - if (_ioProcessors.find(ioProcIter->first) != _ioProcessors.end()) { - ioProcIter++; - continue; - } - - // this might throw error.execution - _ioProcessors[ioProcIter->first] = _factory->createIOProcessor(ioProcIter->first, this); - _ioProcessors[ioProcIter->first].setType(ioProcIter->first); - _ioProcessors[ioProcIter->first].setInterpreter(this); - - if (iequals(ioProcIter->first, "http")) { - // this is somewhat ugly - _httpServlet = static_cast<InterpreterHTTPServlet*>(_ioProcessors[ioProcIter->first]._impl.get()); - } - - if (iequals(ioProcIter->first, "websocket")) { - // this is somewhat ugly - _wsServlet = static_cast<InterpreterWebSocketServlet*>(_ioProcessors[ioProcIter->first]._impl.get()); - } - - // register aliases - std::list<std::string> names = _ioProcessors[ioProcIter->first].getNames(); - std::list<std::string>::iterator nameIter = names.begin(); - while(nameIter != names.end()) { - // do not override - if (!boost::equal(*nameIter, ioProcIter->first) && _ioProcessors.find(*nameIter) == _ioProcessors.end()) - _ioProcessors[*nameIter] = _ioProcessors[ioProcIter->first]; - nameIter++; - } -#if 0 - try { - _dataModel.registerIOProcessor(ioProcIter->first, _ioProcessors[ioProcIter->first]); - } catch (Event e) { - LOG(ERROR) << "Syntax error when setting _ioprocessors:" << std::endl << e << std::endl; - } -#endif - ioProcIter++; - } -} - -IOProcessor InterpreterImpl::getIOProcessor(const std::string& type) { - tthread::lock_guard<tthread::recursive_mutex> lock(_pluginMutex); - if (_ioProcessors.find(type) == _ioProcessors.end()) { - LOG(ERROR) << "No ioProcessor known for type " << type; - return IOProcessor(); - } - return _ioProcessors[type]; -} - -void InterpreterImpl::setCmdLineOptions(std::map<std::string, std::string> params) { - std::map<std::string, std::string>::iterator paramIter = params.begin(); - while (paramIter != params.end()) { - if (paramIter->second.length() > 0) { - _cmdLineOptions.compound[paramIter->first] = Data(paramIter->second, Data::VERBATIM); - } else { - _cmdLineOptions.compound[paramIter->first] = Data("true"); - } - paramIter++; - } -} - -bool InterpreterImpl::hasLegalConfiguration() { - return isLegalConfiguration(_configuration); -} - -bool InterpreterImpl::isLegalConfiguration(const std::list<std::string>& config) { - NodeSet<std::string> states; - for (std::list<std::string>::const_iterator confIter = config.begin(); confIter != config.end(); confIter++) { - Element<std::string> state = getState(*confIter); - if (!state) { - LOG(INFO) << "No state with id '" << *confIter << "'"; - return false; - } - states.push_back(state); - while((state = getParentState(state))) { - states.push_back(state); - }; - } - return isLegalConfiguration(states); -} - -/** - * See: http://www.w3.org/TR/scxml/#LegalStateConfigurations - */ -bool InterpreterImpl::isLegalConfiguration(const NodeSet<std::string>& config) { - - // The configuration contains exactly one child of the <scxml> element. - NodeSet<std::string> scxmlChilds = getChildStates(_scxml); - Node<std::string> foundScxmlChild; - for (size_t i = 0; i < scxmlChilds.size(); i++) { - if (isMember(scxmlChilds[i], config)) { - if (foundScxmlChild) { - LOG(ERROR) << "Invalid configuration: Multiple childs of scxml root are active '" << ATTR_CAST(foundScxmlChild, "id") << "' and '" << ATTR_CAST(scxmlChilds[i], "id") << "'"; - return false; - } - foundScxmlChild = scxmlChilds[i]; - } - } - if (!foundScxmlChild) { - LOG(ERROR) << "Invalid configuration: No childs of scxml root are active"; - - return false; - } - - // The configuration contains one or more atomic states. - bool foundAtomicState = false; - for (size_t i = 0; i < config.size(); i++) { - if (isAtomic(Element<std::string>(config[i]))) { - foundAtomicState = true; - break; - } - } - if (!foundAtomicState) { - LOG(ERROR) << "Invalid configuration: No atomic state is active"; - return false; - } - - // the configuration contains no history pseudo-states - for (size_t i = 0; i < config.size(); i++) { - if (isHistory(Element<std::string>(config[i]))) { - LOG(ERROR) << "Invalid configuration: history state " << ATTR_CAST(config[i], "id") << " is active"; - return false; - } - } - - - - // When the configuration contains an atomic state, it contains all of its <state> and <parallel> ancestors. - for (size_t i = 0; i < config.size(); i++) { - if (isAtomic(Element<std::string>(config[i]))) { - Node<std::string> parent = config[i]; - while(((parent = parent.getParentNode()) && parent.getNodeType() == Node_base::ELEMENT_NODE)) { - if (isState(Element<std::string>(parent)) && - (iequals(LOCALNAME(parent), "state") || - iequals(LOCALNAME(parent), "parallel"))) { - if (!isMember(parent, config)) { - LOG(ERROR) << "Invalid configuration: atomic state '" << ATTR_CAST(config[i], "id") << "' is active, but parent '" << ATTR_CAST(parent, "id") << "' is not"; - return false; - } - } - } - } - } - - // When the configuration contains a non-atomic <state>, it contains one and only one of the state's children - for (size_t i = 0; i < config.size(); i++) { - Element<std::string> configElem(config[i]); - if (!isAtomic(configElem) && !isParallel(configElem)) { - Node<std::string> foundChildState; - //std::cerr << config[i] << std::endl; - NodeSet<std::string> childs = getChildStates(config[i]); - for (size_t j = 0; j < childs.size(); j++) { - //std::cerr << childs[j] << std::endl; - if (isMember(childs[j], config)) { - if (foundChildState) { - LOG(ERROR) << "Invalid configuration: Multiple childs of compound '" << ATTR_CAST(config[i], "id") - << "' are active '" << ATTR_CAST(foundChildState, "id") << "' and '" << ATTR_CAST(childs[j], "id") << "'"; - return false; - } - foundChildState = childs[j]; - } - } - if (!foundChildState) { - LOG(ERROR) << "Invalid configuration: No childs of compound '" << ATTR_CAST(config[i], "id") << "' are active"; - return false; - } - } - } - - // If the configuration contains a <parallel> state, it contains all of its children - for (size_t i = 0; i < config.size(); i++) { - if (isParallel(Element<std::string>(config[i]))) { - NodeSet<std::string> childs = getChildStates(config[i]); - for (size_t j = 0; j < childs.size(); j++) { - if (!isMember(childs[j], config) && !isHistory(Element<std::string>(childs[j]))) { - LOG(ERROR) << "Invalid configuration: Not all children of parallel '" << ATTR_CAST(config[i], "id") << "' are active i.e. '" << ATTR_CAST(childs[j], "id") << "' is not"; - return false; - } - } - } - } - - // everything worked out fine! - return true; -} - -bool InterpreterImpl::isInState(const std::string& stateId) { - if (HAS_ATTR(_scxml, "flat") && stringIsTrue(ATTR(_scxml, "flat"))) { - // extension for flattened SCXML documents - if (_configuration.size() > 0 && HAS_ATTR_CAST(_configuration[0], "id")) { - // all states are encoded in the current statename - FlatStateIdentifier flatId(ATTR_CAST(_configuration[0], "id")); - for (std::list<std::string>::const_iterator iter = flatId.getActive().begin(); iter != flatId.getActive().end(); iter++) { - if (iequals(stateId, *iter)) - return true; - } - } - return false; - } else { - - for (size_t i = 0; i < _configuration.size(); i++) { - if (HAS_ATTR_CAST(_configuration[i], "id") && - iequals(ATTR_CAST(_configuration[i], "id"), stateId)) { - return true; - } - } - } - return false; -} - -const URL& InterpreterImpl::getBaseURLForNode(const Arabica::DOM::Node<std::string>& node) { - Arabica::DOM::Node<std::string> currNode = node; - while(currNode) { - if (_baseURL.find(currNode) != _baseURL.end()) { - // speed-up subsequent lookups - _baseURL[node] = _baseURL[currNode]; - // return baseurl - return _baseURL[currNode]; - } - currNode = currNode.getParentNode(); - } - return _baseURL[_scxml]; -} - -void InterpreterImpl::handleDOMEvent(Arabica::DOM::Events::Event<std::string>& event) { - // clear targets - _cachedTargets.clear(); - - if (event.getType().compare("DOMAttrModified") == 0) // we do not care about other attributes - return; - - // remove modified states from cache - Node<std::string> target = Arabica::DOM::Node<std::string>(event.getTarget()); - NodeSet<std::string> childs = DOMUtils::filterChildElements(_nsInfo.xmlNSPrefix + "state", target); - for (size_t i = 0; i < childs.size(); i++) { - if (HAS_ATTR_CAST(childs[i], "id")) { - _cachedStates.erase(ATTR_CAST(childs[i], "id")); - } - } - // it's more stress to search through all pairs, just have them recalculated - _cachedProperAncestors.clear(); -} - -void InterpreterImpl::DOMEventListener::handleEvent(Arabica::DOM::Events::Event<std::string>& event) { - if (_interpreter) - _interpreter->handleDOMEvent(event); -} - -std::ostream& operator<< (std::ostream& os, const InterpreterState& interpreterState) { - os << "[" << InterpreterImpl::stateToString(interpreterState) << "]" << std::endl; - return os; -} - -std::string InterpreterImpl::stateToString(InterpreterState state) { - std::stringstream ss; - - switch(state) { - case USCXML_INSTANTIATED: - ss << "INSTANTIATED"; - break; - case USCXML_MICROSTEPPED: - ss << "MICROSTEPPED"; - break; - case USCXML_MACROSTEPPED: - ss << "MACROSTEPPED"; - break; - case USCXML_IDLE: - ss << "IDLE"; - break; - case USCXML_FINISHED: - ss << "FINISHED"; - break; - case USCXML_DESTROYED: - ss << "DESTROYED"; - break; - default: - ss << "INVALID"; - break; - } - - return ss.str(); -} - - } |