/** * @file * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de) * @copyright Simplified BSD * * @cond * This program is free software: you can redistribute it and/or modify * it under the terms of the FreeBSD license as published by the FreeBSD * project. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * You should have received a copy of the FreeBSD license along with this * program. If not, see . * @endcond */ #ifndef RUNTIME_H_SQ1MBKGN #define RUNTIME_H_SQ1MBKGN // this has to be the first include or MSVC will run amok #include "uscxml/Common.h" #include // arabica xpath uses cerr without iostream #include #include #include #include #include #include #include #include #include #include "uscxml/concurrency/BlockingQueue.h" #include "uscxml/messages/Data.h" #include "uscxml/messages/SendRequest.h" #include "uscxml/URL.h" #include "uscxml/plugins/DataModel.h" #include "uscxml/plugins/IOProcessor.h" #include "uscxml/plugins/Invoker.h" #include "uscxml/plugins/ExecutableContent.h" #define USCXML_MONITOR_CATCH(callback) \ catch (Event e) { \ LOG(ERROR) << "Syntax error when calling " #callback " on monitors: " << std::endl << e << std::endl; \ } catch (boost::bad_weak_ptr e) { \ LOG(ERROR) << "Unclean shutdown " << std::endl << std::endl; \ } catch (...) { \ LOG(ERROR) << "An exception occured when calling " #callback " on monitors"; \ } \ if (_state == USCXML_DESTROYED) { \ throw boost::bad_weak_ptr(); \ } \ #define USCXML_MONITOR_CALLBACK(callback)\ for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { \ try { \ (*monIter)->callback(shared_from_this()); \ } \ USCXML_MONITOR_CATCH(callback) \ } #define USCXML_MONITOR_CALLBACK2(callback, arg1)\ for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { \ try { \ (*monIter)->callback(shared_from_this(), arg1); \ } \ USCXML_MONITOR_CATCH(callback) \ } #define USCXML_MONITOR_CALLBACK3(callback, arg1, arg2)\ for(monIter_t monIter = _monitors.begin(); monIter != _monitors.end(); monIter++) { \ try { \ (*monIter)->callback(shared_from_this(), arg1, arg2); \ } \ USCXML_MONITOR_CATCH(callback) \ } namespace uscxml { class HTTPServletInvoker; class InterpreterMonitor; class InterpreterHTTPServlet; class InterpreterWebSocketServlet; class Factory; class DelayedEventQueue; enum Capabilities { CAN_NOTHING = 0, CAN_BASIC_HTTP = 1, CAN_GENERIC_HTTP = 2, }; class USCXML_API InterpreterOptions { public: InterpreterOptions() : withDebugger(false), verbose(false), withHTTP(true), withHTTPS(true), withWS(true), logLevel(0), httpPort(0), httpsPort(0), wsPort(0) { } bool withDebugger; bool verbose; bool withHTTP; bool withHTTPS; bool withWS; int logLevel; unsigned short httpPort; unsigned short httpsPort; unsigned short wsPort; std::string pluginPath; std::string certificate; std::string privateKey; std::string publicKey; std::map interpreters; std::map additionalParameters; std::string error; operator bool() { return error.length() == 0; } static void printUsageAndExit(const char* progName); static InterpreterOptions fromCmdLine(int argc, char** argv); unsigned int getCapabilities(); }; class USCXML_API NameSpaceInfo { public: NameSpaceInfo() : nsContext(NULL) { init(std::map()); } NameSpaceInfo(const std::map& nsInfo) : nsContext(NULL) { init(nsInfo); } NameSpaceInfo(const NameSpaceInfo& other) : nsContext(NULL) { init(other.nsInfo); } virtual ~NameSpaceInfo() { if (nsContext) delete nsContext; } NameSpaceInfo& operator=( const NameSpaceInfo& other ) { init(other.nsInfo); return *this; } void setPrefix(Arabica::DOM::Element element) { if (nsURL.size() > 0) element.setPrefix(nsToPrefix[nsURL]); } void setPrefix(Arabica::DOM::Attr attribute) { if (nsURL.size() > 0) attribute.setPrefix(nsToPrefix[nsURL]); } std::string getXMLPrefixForNS(const std::string& ns) const { if (nsToPrefix.find(ns) != nsToPrefix.end() && nsToPrefix.at(ns).size()) return nsToPrefix.at(ns) + ":"; return ""; } const Arabica::XPath::StandardNamespaceContext* getNSContext() { return nsContext; } std::string nsURL; // ough to be "http://www.w3.org/2005/07/scxml" but maybe empty std::string xpathPrefix; // prefix mapped for xpath, "scxml" is _xmlNSPrefix is empty but _nsURL set std::string xmlNSPrefix; // the actual prefix for elements in the xml file std::map nsToPrefix; // prefixes for a given namespace std::map nsInfo; // all xmlns mappings private: Arabica::XPath::StandardNamespaceContext* nsContext; void init(const std::map& nsInfo); }; enum InterpreterState { USCXML_DESTROYED = -2, ///< destructor ran - users should never see this one USCXML_FINISHED = -1, ///< machine reached a final configuration and is done USCXML_IDLE = 0, ///< stable configuration and queues empty USCXML_INSTANTIATED = 1, ///< nothing really, just instantiated USCXML_MICROSTEPPED = 2, ///< processed one transition set USCXML_MACROSTEPPED = 4, ///< processed all transition sets and reached a stable configuration }; USCXML_API std::ostream& operator<< (std::ostream& os, const InterpreterState& interpreterState); class USCXML_API InterpreterImpl : public boost::enable_shared_from_this { public: typedef std::set::iterator monIter_t; enum Binding { EARLY = 0, LATE = 1 }; virtual ~InterpreterImpl(); void copyTo(InterpreterImpl* other); void copyTo(boost::shared_ptr other); // TODO: We need to move the destructor to the implementations to make these pure virtual virtual InterpreterState interpret() { return _state; ///< Start interpreter blockingly } virtual InterpreterState step(int waitForMS = 0) { return _state; }; ///< Perform a single step void start(); ///< Start interpretation in a thread void stop(); ///< Stop interpreter thread void reset(); ///< Reset state machine void join(); bool isRunning(); InterpreterState getInterpreterState(); void addMonitor(InterpreterMonitor* monitor) { _monitors.insert(monitor); } void removeMonitor(InterpreterMonitor* monitor) { _monitors.erase(monitor); } void setSourceURI(std::string sourceURI) { _sourceURI = URL(sourceURI); URL baseURI(sourceURI); URL::toBaseURL(baseURI); _baseURI = baseURI; } std::string getBaseURI() { return _baseURI.asString(); } std::string getSourceURI() { return _sourceURI.asString(); } void setCmdLineOptions(std::map params); Data getCmdLineOptions() { return _cmdLineOptions; } InterpreterHTTPServlet* getHTTPServlet() { return _httpServlet; } DataModel getDataModel() { return _dataModel; } void setParentQueue(uscxml::concurrency::BlockingQueue* parentQueue) { _parentQueue = parentQueue; } void setFactory(Factory* factory) { _factory = factory; } Factory* getFactory() { return _factory; } Arabica::XPath::NodeSet getNodeSetForXPath(const std::string& xpathExpr) { return _xpath.evaluate(xpathExpr, _scxml).asNodeSet(); } void setNameSpaceInfo(const NameSpaceInfo& nsInfo) { _nsInfo = nsInfo; _xpath.setNamespaceContext(*_nsInfo.getNSContext()); } NameSpaceInfo getNameSpaceInfo() const { return _nsInfo; } void receiveInternal(const Event& event); void receive(const Event& event, bool toFront = false); Event getCurrentEvent() { tthread::lock_guard lock(_mutex); return _currEvent; } virtual bool isInState(const std::string& stateId); Arabica::XPath::NodeSet getConfiguration() { tthread::lock_guard lock(_mutex); return _configuration; } Arabica::XPath::NodeSet getBasicConfiguration() { tthread::lock_guard lock(_mutex); Arabica::XPath::NodeSet basicConfig; for (int i = 0; i < _configuration.size(); i++) { if (isAtomic(_configuration[i])) basicConfig.push_back(_configuration[i]); } return basicConfig; } void setInitalConfiguration(const std::list& states) { _startConfiguration = states; } void setInvokeRequest(const InvokeRequest& req) { _invokeReq = req; } virtual Arabica::DOM::Document getDocument() const { return _document; } void setCapabilities(unsigned int capabilities) { _capabilities = capabilities; } void setName(const std::string& name); const std::string& getName() { return _name; } const std::string& getSessionId() { return _sessionId; } DelayedEventQueue* getDelayQueue() { return _sendQueue; } const std::map& getIOProcessors() { return _ioProcessors; } const std::map& getInvokers() { return _invokers; } bool runOnMainThread(int fps, bool blocking = true); static bool isMember(const Arabica::DOM::Node& node, const Arabica::XPath::NodeSet& set); bool hasLegalConfiguration(); bool isLegalConfiguration(const Arabica::XPath::NodeSet&); bool isLegalConfiguration(const std::list&); static bool isState(const Arabica::DOM::Node& state); static bool isPseudoState(const Arabica::DOM::Node& state); static bool isTransitionTarget(const Arabica::DOM::Node& elem); static bool isTargetless(const Arabica::DOM::Node& transition); static bool isAtomic(const Arabica::DOM::Node& state); static bool isFinal(const Arabica::DOM::Node& state); static bool isHistory(const Arabica::DOM::Node& state); static bool isParallel(const Arabica::DOM::Node& state); static bool isCompound(const Arabica::DOM::Node& state); static bool isDescendant(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); bool isInEmbeddedDocument(const Arabica::DOM::Node& node); bool isInitial(const Arabica::DOM::Node& state); static std::string stateToString(InterpreterState state); Arabica::DOM::Node getState(const std::string& stateId); Arabica::XPath::NodeSet getStates(const std::list& stateIds); Arabica::XPath::NodeSet getAllStates(); Arabica::XPath::NodeSet getInitialStates(Arabica::DOM::Node state = Arabica::DOM::Node()); static Arabica::XPath::NodeSet getChildStates(const Arabica::DOM::Node& state); static Arabica::XPath::NodeSet getChildStates(const Arabica::XPath::NodeSet& state); static Arabica::DOM::Node getParentState(const Arabica::DOM::Node& element); static Arabica::DOM::Node getAncestorElement(const Arabica::DOM::Node& node, const std::string tagName); virtual Arabica::XPath::NodeSet getTargetStates(const Arabica::DOM::Node& transition); virtual Arabica::XPath::NodeSet getTargetStates(const Arabica::XPath::NodeSet& transitions); virtual Arabica::DOM::Node getSourceState(const Arabica::DOM::Node& transition); static Arabica::XPath::NodeSet filterChildElements(const std::string& tagname, const Arabica::DOM::Node& node, bool recurse = false); static Arabica::XPath::NodeSet filterChildElements(const std::string& tagName, const Arabica::XPath::NodeSet& nodeSet, bool recurse = false); static Arabica::XPath::NodeSet filterChildType(const Arabica::DOM::Node_base::Type type, const Arabica::DOM::Node& node, bool recurse = false); static Arabica::XPath::NodeSet filterChildType(const Arabica::DOM::Node_base::Type type, const Arabica::XPath::NodeSet& nodeSet, bool recurse = false); static std::list tokenizeIdRefs(const std::string& idRefs); static std::string spaceNormalize(const std::string& text); static bool nameMatch(const std::string& transitionEvent, const std::string& event); Arabica::DOM::Node findLCCA(const Arabica::XPath::NodeSet& states); virtual Arabica::XPath::NodeSet getProperAncestors(const Arabica::DOM::Node& s1, const Arabica::DOM::Node& s2); protected: static void run(void*); // static method for thread to run class DOMEventListener : public Arabica::DOM::Events::EventListener { public: void handleEvent(Arabica::DOM::Events::Event& event); InterpreterImpl* _interpreter; }; InterpreterImpl(); void init(); void setupAndNormalizeDOM(); virtual void setupIOProcessors(); void initializeData(const Arabica::DOM::Element& data); void finalizeAndAutoForwardCurrentEvent(); void setInterpreterState(InterpreterState newState); bool _stable; tthread::thread* _thread; tthread::recursive_mutex _mutex; tthread::condition_variable _condVar; tthread::recursive_mutex _pluginMutex; InterpreterState _state; URL _baseURI; URL _sourceURI; Arabica::DOM::Document _document; Arabica::DOM::Element _scxml; Arabica::XPath::XPath _xpath; NameSpaceInfo _nsInfo; bool _topLevelFinalReached; bool _isInitialized; bool _domIsSetup; bool _isStarted; bool _isRunning; InterpreterImpl::Binding _binding; Arabica::XPath::NodeSet _configuration; Arabica::XPath::NodeSet _alreadyEntered; Arabica::XPath::NodeSet _statesToInvoke; std::list _startConfiguration; InvokeRequest _invokeReq; DataModel _dataModel; std::map > _historyValue; std::list _internalQueue; uscxml::concurrency::BlockingQueue _externalQueue; uscxml::concurrency::BlockingQueue* _parentQueue; DelayedEventQueue* _sendQueue; DOMEventListener _domEventListener; Event _currEvent; Factory* _factory; InterpreterHTTPServlet* _httpServlet; InterpreterWebSocketServlet* _wsServlet; std::set _monitors; long _lastRunOnMainThread; std::string _name; std::string _sessionId; unsigned int _capabilities; Data _cmdLineOptions; virtual void executeContent(const Arabica::DOM::Node& content, bool rethrow = false); virtual void executeContent(const Arabica::DOM::NodeList& content, bool rethrow = false); virtual void executeContent(const Arabica::XPath::NodeSet& content, bool rethrow = false); void processContentElement(const Arabica::DOM::Node& element, Arabica::DOM::Node& dom, std::string& text, std::string& expr); void processParamChilds(const Arabica::DOM::Node& element, std::multimap& params); void processDOMorText(const Arabica::DOM::Node& element, Arabica::DOM::Node& dom, std::string& text); virtual void send(const Arabica::DOM::Node& element); virtual void invoke(const Arabica::DOM::Node& element); virtual void cancelInvoke(const Arabica::DOM::Node& element); virtual void internalDoneSend(const Arabica::DOM::Node& state); static void delayedSend(void* userdata, std::string eventName); void returnDoneEvent(const Arabica::DOM::Node& state); bool hasConditionMatch(const Arabica::DOM::Node& conditional); bool isInFinalState(const Arabica::DOM::Node& state); bool parentIsScxmlState(const Arabica::DOM::Node& state); IOProcessor getIOProcessor(const std::string& type); std::map _ioProcessors; std::map > _sendIds; std::map _invokers; std::map, ExecutableContent> _executableContent; /// TODO: We need to adapt them when the DOM is operated upon std::map > _cachedStates; std::map _cachedURLs; friend class USCXMLInvoker; friend class SCXMLIOProcessor; friend class Interpreter; }; class USCXML_API Interpreter { public: static Interpreter fromDOM(const Arabica::DOM::Document& dom, const NameSpaceInfo& nameSpaceInfo); static Interpreter fromXML(const std::string& xml); static Interpreter fromURI(const std::string& uri); static Interpreter fromClone(const Interpreter& other); Interpreter() : _impl() {} // the empty, invalid interpreter Interpreter(boost::shared_ptr const impl) : _impl(impl) { } Interpreter(const Interpreter& other) : _impl(other._impl) { } virtual ~Interpreter() {}; operator bool() const { return (_impl && _impl->_state != USCXML_DESTROYED); } bool operator< (const Interpreter& other) const { return _impl < other._impl; } bool operator==(const Interpreter& other) const { return _impl == other._impl; } bool operator!=(const Interpreter& other) const { return _impl != other._impl; } Interpreter& operator= (const Interpreter& other) { _impl = other._impl; return *this; } void reset() { return _impl->reset(); } void start() { return _impl->start(); } void stop() { return _impl->stop(); } // void join() { // return _impl->join(); // }; bool isRunning() { return _impl->isRunning(); } void interpret() { _impl->interpret(); }; InterpreterState step(int waitForMS = 0) { return _impl->step(waitForMS); }; InterpreterState step(bool blocking) { if (blocking) return _impl->step(-1); return _impl->step(0); }; InterpreterState getState() { return _impl->getInterpreterState(); } void addMonitor(InterpreterMonitor* monitor) { return _impl->addMonitor(monitor); } void removeMonitor(InterpreterMonitor* monitor) { return _impl->removeMonitor(monitor); } void setSourceURI(std::string sourceURI) { return _impl->setSourceURI(sourceURI); } std::string getSourceURI() { return _impl->getSourceURI(); } std::string getBaseURI() { return _impl->getBaseURI(); } void setNameSpaceInfo(const NameSpaceInfo& nsInfo) { _impl->setNameSpaceInfo(nsInfo); } NameSpaceInfo getNameSpaceInfo() const { return _impl->getNameSpaceInfo(); } void setCmdLineOptions(std::map params) { return _impl->setCmdLineOptions(params); } Data getCmdLineOptions() { return _impl->getCmdLineOptions(); } InterpreterHTTPServlet* getHTTPServlet() { return _impl->getHTTPServlet(); } DataModel getDataModel() { return _impl->getDataModel(); } void setParentQueue(uscxml::concurrency::BlockingQueue* parentQueue) { return _impl->setParentQueue(parentQueue); } void setFactory(Factory* factory) { return _impl->setFactory(factory); } Factory* getFactory() { return _impl->getFactory(); } Arabica::XPath::NodeSet getNodeSetForXPath(const std::string& xpathExpr) { return _impl->getNodeSetForXPath(xpathExpr); } void inline receiveInternal(const Event& event) { return _impl->receiveInternal(event); } void receive(const Event& event, bool toFront = false) { return _impl->receive(event, toFront); } Event getCurrentEvent() { return _impl->getCurrentEvent(); } bool isInState(const std::string& stateId) { return _impl->isInState(stateId); } Arabica::XPath::NodeSet getConfiguration() { return _impl->getConfiguration(); } Arabica::XPath::NodeSet getBasicConfiguration() { return _impl->getBasicConfiguration(); } void setInitalConfiguration(const std::list& states) { return _impl->setInitalConfiguration(states); } // Arabica::DOM::Node getState(const std::string& stateId) { // return _impl->getState(stateId); // } // Arabica::XPath::NodeSet getStates(const std::list& stateIds) { // return _impl->getStates(stateIds); // } // Arabica::XPath::NodeSet getAllStates() { // return _impl->getAllStates(); // } Arabica::DOM::Document getDocument() const { return _impl->getDocument(); } void setCapabilities(unsigned int capabilities) { return _impl->setCapabilities(capabilities); } void setName(const std::string& name) { return _impl->setName(name); } const std::string& getName() { return _impl->getName(); } const std::string& getSessionId() { return _impl->getSessionId(); } DelayedEventQueue* getDelayQueue() { return _impl->getDelayQueue(); } const std::map& getIOProcessors() { return _impl->getIOProcessors(); } const std::map& getInvokers() { return _impl->getInvokers(); } bool runOnMainThread(int fps, bool blocking = true) { return _impl->runOnMainThread(fps, blocking); } bool hasLegalConfiguration() { return _impl->hasLegalConfiguration(); } bool isLegalConfiguration(const Arabica::XPath::NodeSet& config) { return _impl->isLegalConfiguration(config); } bool isLegalConfiguration(const std::list& config) { return _impl->isLegalConfiguration(config); } boost::shared_ptr getImpl() const { return _impl; } static std::map > getInstances(); protected: void setInvokeRequest(const InvokeRequest& req) { return _impl->setInvokeRequest(req); } static Interpreter fromInputSource(Arabica::SAX::InputSource& source); boost::shared_ptr _impl; static std::map > _instances; static tthread::recursive_mutex _instanceMutex; }; class USCXML_API InterpreterMonitor { public: virtual ~InterpreterMonitor() {} virtual void beforeProcessingEvent(Interpreter interpreter, const Event& event) {} virtual void beforeMicroStep(Interpreter interpreter) {} virtual void beforeExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void afterExitingState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void beforeExecutingContent(Interpreter interpreter, const Arabica::DOM::Element& element) {} virtual void afterExecutingContent(Interpreter interpreter, const Arabica::DOM::Element& element) {} virtual void beforeUninvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void afterUninvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void beforeTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) {} virtual void afterTakingTransition(Interpreter interpreter, const Arabica::DOM::Element& transition, bool moreComing) {} virtual void beforeEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void afterEnteringState(Interpreter interpreter, const Arabica::DOM::Element& state, bool moreComing) {} virtual void beforeInvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void afterInvoking(Interpreter interpreter, const Arabica::DOM::Element& invokeElem, const std::string& invokeid) {} virtual void afterMicroStep(Interpreter interpreter) {} virtual void onStableConfiguration(Interpreter interpreter) {} virtual void beforeCompletion(Interpreter interpreter) {} virtual void afterCompletion(Interpreter interpreter) {} }; } #endif