diff options
Diffstat (limited to 'src')
26 files changed, 4793 insertions, 0 deletions
diff --git a/src/bindings/CMakeLists.txt b/src/bindings/CMakeLists.txt new file mode 100644 index 0000000..deac396 --- /dev/null +++ b/src/bindings/CMakeLists.txt @@ -0,0 +1,12 @@ +find_package(SWIG) +if (SWIG_FOUND) + if(SWIG_VERSION VERSION_GREATER 2.0.4) + MARK_AS_ADVANCED(SWIG_DIR SWIG_EXECUTABLE SWIG_VERSION) + INCLUDE(${SWIG_USE_FILE}) + add_subdirectory(swig/java) + else() + message("SWIG version 2.0.5 is required, found ${SWIG_VERSION} - skipping java wrapper generation") + endif() +else() + message("SWIG not found - skipping wrapper generation") +endif() diff --git a/src/bindings/swig/java/CMakeLists.txt b/src/bindings/swig/java/CMakeLists.txt new file mode 100644 index 0000000..a729b8e --- /dev/null +++ b/src/bindings/swig/java/CMakeLists.txt @@ -0,0 +1,31 @@ +# generate JNI library and create a jar +# Make from within Eclipse fails miserably with the whole thing + +find_package(JNI) +if(JNI_FOUND) + include_directories(${JNI_INCLUDE_DIRS}) +else() + message(STATUS "No JNI libraries found - not building Java wrappers") + return() +endif() + +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + +SET(CMAKE_SWIG_FLAGS "") +SET(SCXMLPL_JAVA_PACKAGE "org.uscxml") +SET(SCXMLPL_JAVA_DIR "org/uscxml") + +# we need ; to produce a space with the package .. weird +SET_SOURCE_FILES_PROPERTIES(uscxml.i PROPERTIES SWIG_FLAGS "-package;${SCXMLPL_JAVA_PACKAGE}") +SET_SOURCE_FILES_PROPERTIES(uscxml.i PROPERTIES CPLUSPLUS ON) +SET(CMAKE_SWIG_OUTDIR "${CMAKE_CURRENT_BINARY_DIR}/${SCXMLPL_JAVA_DIR}") + +SWIG_ADD_MODULE(uscxmlNativeJava java uscxml.i) +foreach(JNI_LIBRARY ${JNI_LIBRARIES}) + if (NOT ${JNI_LIBRARY} MATCHES ".*jawt.*") + SWIG_LINK_LIBRARIES(uscxmlNativeJava ${JNI_LIBRARY}) + endif() +endforeach() +set_target_properties(uscxmlNativeJava PROPERTIES FOLDER "Bindings") + +swig_link_libraries(uscxmlNativeJava uscxml) diff --git a/src/bindings/swig/java/uscxml.i b/src/bindings/swig/java/uscxml.i new file mode 100644 index 0000000..ccd6fe0 --- /dev/null +++ b/src/bindings/swig/java/uscxml.i @@ -0,0 +1,40 @@ +%module(directors="1", allprotected="1") uscxmlNativeJava + +// import swig typemaps +//%include <arrays_java.i> +//%include <inttypes.i> +%include <boost_shared_ptr.i> + +// disable warning related to unknown base class +#pragma SWIG nowarn=401 +//%ignore boost::enable_shared_from_this; + +%javaconst(1); + +# %shared_ptr(uscxml::dom::Element); +# %shared_ptr(uscxml::dom::Executable); + + +//************************************************** +// This ends up in the generated wrapper code +//************************************************** + +%{ + +#include "../../../uscxml/Message.h" +#include "../../../uscxml/Interpreter.h" + +using namespace uscxml; + +%} + +%rename(toString) operator<<; + + +//*********************************************** +// Parse the header file to generate wrappers +//*********************************************** + +%include "../../../uscxml/Message.h" +%include "../../../uscxml/Interpreter.h" + diff --git a/src/uscxml.h b/src/uscxml.h new file mode 100644 index 0000000..792ba43 --- /dev/null +++ b/src/uscxml.h @@ -0,0 +1,6 @@ +#ifndef USCXML_H_2WZ0PBQH +#define USCXML_H_2WZ0PBQH + +#include "uscxml/Interpreter.h" + +#endif /* end of include guard: USCXML_H_2WZ0PBQH */ diff --git a/src/uscxml/Factory.cpp b/src/uscxml/Factory.cpp new file mode 100644 index 0000000..91da562 --- /dev/null +++ b/src/uscxml/Factory.cpp @@ -0,0 +1,56 @@ +#include "uscxml/Factory.h" +#include "uscxml/datamodel/ecmascript/v8/V8DataModel.h" +//#include "uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h" +#include "uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h" + +namespace uscxml { + + Factory::Factory() { + _dataModels["ecmascript"] = new V8DataModel(); +// _ioProcessors["basichttp"] = new PionIOProcessor(); + _ioProcessors["basichttp"] = new io::libevent::EventIOProcessor(); + } + + void Factory::registerIOProcessor(const std::string type, IOProcessor* ioProcessor) { + getInstance()->_ioProcessors[type] = ioProcessor; + } + + void Factory::registerDataModel(const std::string type, DataModel* dataModel) { + getInstance()->_dataModels[type] = dataModel; + } + + void Factory::registerExecutableContent(const std::string tag, ExecutableContent* executableContent) { + getInstance()->_executableContent[tag] = executableContent; + } + + DataModel* Factory::getDataModel(const std::string type, Interpreter* interpreter) { + if (Factory::getInstance()->_dataModels.find(type) != getInstance()->_dataModels.end()) { + return getInstance()->_dataModels[type]->create(interpreter); + } + return NULL; + } + + IOProcessor* Factory::getIOProcessor(const std::string type, Interpreter* interpreter) { + if (getInstance()->_ioProcessors.find(type) != getInstance()->_ioProcessors.end()) { + return getInstance()->_ioProcessors[type]->create(interpreter); + } + return NULL; + } + + ExecutableContent* Factory::getExecutableContent(const std::string tag, Interpreter* interpreter) { + if (getInstance()->_executableContent.find(tag) != getInstance()->_executableContent.end()) { + return getInstance()->_executableContent[tag]->create(interpreter); + } + return NULL; + } + + Factory* Factory::getInstance() { + if (_instance == NULL) { + _instance = new Factory(); + } + return _instance; + } + + Factory* Factory::_instance = NULL; + +}
\ No newline at end of file diff --git a/src/uscxml/Factory.h b/src/uscxml/Factory.h new file mode 100644 index 0000000..f1840c6 --- /dev/null +++ b/src/uscxml/Factory.h @@ -0,0 +1,73 @@ +#ifndef FACTORY_H_5WKLGPRB +#define FACTORY_H_5WKLGPRB + +#include "uscxml/Message.h" + +#include <string> + +namespace uscxml { + + class Interpreter; + + class ExecutableContent { + public: + ExecutableContent() {}; + virtual ExecutableContent* create(Interpreter* interpreter) = 0; + }; + + class IOProcessor { + public: + IOProcessor() {}; + virtual ~IOProcessor() {}; + virtual IOProcessor* create(Interpreter* interpreter) = 0; + + virtual std::string getURL() = 0; + virtual void send(SendRequest& req) = 0; + virtual void invoke(InvokeRequest& req) = 0; + virtual void cancel(const std::string sendId) = 0; + }; + + class DataModel { + public: + virtual DataModel* create(Interpreter* interpreter) = 0; + + virtual bool validate(const std::string& location, const std::string& schema) = 0; + virtual void setEvent(Event& event) = 0; + virtual void setData(const std::string& key, Data& event) = 0; + + // foreach + virtual uint32_t getLength(const std::string& expr) = 0; + virtual void pushContext() = 0; + virtual void popContext() = 0; + + virtual void eval(const std::string& expr) = 0; + virtual std::string evalAsString(const std::string& expr) = 0; + virtual bool evalAsBool(const std::string& expr) = 0; + virtual void assign(const std::string& location, const std::string& expr) = 0; + }; + + class Factory { + public: + static void registerIOProcessor(const std::string type, IOProcessor* ioProcessor); + static void registerDataModel(const std::string type, DataModel* dataModel); + static void registerExecutableContent(const std::string tag, ExecutableContent* executableContent); + + static DataModel* getDataModel(const std::string type, Interpreter* interpreter); + static IOProcessor* getIOProcessor(const std::string type, Interpreter* interpreter); + static ExecutableContent* getExecutableContent(const std::string tag, Interpreter* interpreter); + static Factory* getInstance(); + + std::map<std::string, DataModel*> _dataModels; + std::map<std::string, IOProcessor*> _ioProcessors; + std::map<std::string, ExecutableContent*> _executableContent; + + protected: + Factory(); + static Factory* _instance; + + }; + + +} + +#endif /* end of include guard: FACTORY_H_5WKLGPRB */ diff --git a/src/uscxml/Interpreter.cpp b/src/uscxml/Interpreter.cpp new file mode 100644 index 0000000..7189f12 --- /dev/null +++ b/src/uscxml/Interpreter.cpp @@ -0,0 +1,1712 @@ +#include "uscxml/Interpreter.h" + +#include <DOM/Simple/DOMImplementation.hpp> + +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/uuid_generators.hpp> +#include <boost/uuid/uuid_io.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/tokenizer.hpp> + +#include <glog/logging.h> + +#include <assert.h> +#include <algorithm> + +namespace uscxml { + +using namespace Arabica::XPath; +using namespace Arabica::DOM; + +boost::uuids::random_generator Interpreter::uuidGen; +const std::string Interpreter::getUUID() { + return boost::lexical_cast<std::string>(uuidGen()); +} + +Interpreter::Interpreter(const std::string& url) { + _thread = NULL; + _url = url; + + LOG(INFO) << "runtime started with " << _url; + Arabica::SAX::InputSource<std::string> inputSource(_url); + + Arabica::SAX2DOM::Parser<std::string> domParser; + Arabica::SAX::CatchErrorHandler<std::string> errorHandler; + domParser.setErrorHandler(errorHandler); + if(!domParser.parse(inputSource) || !domParser.getDocument().hasChildNodes()) { + LOG(INFO) << "could not parse " << _url << ":"; + if(errorHandler.errorsReported()) { + LOG(ERROR) << errorHandler.errors() << std::endl; + } else { + Arabica::SAX::InputSourceResolver resolver(inputSource, Arabica::default_string_adaptor<std::string>()); + if (!resolver.resolve()) { + LOG(ERROR) << "no such file"; + } + } + } else { + _doc = domParser.getDocument(); + } + + if (_doc) { + // do we have a xmlns attribute? + std::string ns = _doc.getDocumentElement().getNamespaceURI(); + if(ns.size() > 0) { + _nsContext.addNamespaceDeclaration(ns, "sc"); + _xpath.setNamespaceContext(_nsContext); + _nsPrefix = "sc:"; + } + NodeList<std::string> scxmls = _doc.getElementsByTagName("scxml"); + if (scxmls.getLength() > 0) { + _scxml = (Arabica::DOM::Element<std::string>)scxmls.item(0); + } else { + LOG(ERROR) << "Cannot find SCXML element" << std::endl; + } + _name = (HAS_ATTR(_scxml, "name") ? ATTR(_scxml, "name") : getUUID()); + } +} + +Interpreter::~Interpreter() { + std::map<std::string, IOProcessor*>::iterator ioProcessorIter = _ioProcessors.begin(); + while(ioProcessorIter != _ioProcessors.end()) { + delete ioProcessorIter->second; + ioProcessorIter++; + } + if (_thread) { + _thread->join(); + delete(_thread); + } +} + +void Interpreter::start() { + _thread = new tthread::thread(Interpreter::run, this); +} + +void Interpreter::stop() { +} + +void Interpreter::run(void* instance) { + ((Interpreter*)instance)->interpret(); +} + +void Interpreter::waitForStabilization() { + tthread::lock_guard<tthread::mutex> lock(_mutex); + _stabilized.wait(_mutex); +} + +// see: http://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation +void Interpreter::interpret() { + if (!_scxml) + return; +// dump(); + + _sessionId = getUUID(); + normalize(_doc); + + if(HAS_ATTR(_scxml, "datamodel")) { + _dataModel = Factory::getDataModel(ATTR(_scxml, "datamodel"), this); + if(_dataModel == NULL) { + LOG(ERROR) << "No datamodel for " << ATTR(_scxml, "datamodel") << " registered"; + return; + } + } + + setupIOProcessors(); + + // executeGlobalScriptElements + NodeSet<std::string> globalScriptElems = _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "script", _doc).asNodeSet(); + for (unsigned int i = 0; i < globalScriptElems.size(); i++) { +// std::cout << globalScriptElems[i].getFirstChild().getNodeValue() << std::endl; + if (_dataModel) + executeContent(globalScriptElems[i]); + } + + _running = true; + + std::string binding = _xpath.evaluate("/" + _nsPrefix + "scxml/@binding", _doc).asString(); + _binding = (boost::iequals(binding, "late") ? LATE : EARLY); + + // initialize all data elements + if (_dataModel && _binding == EARLY) { + NodeSet<std::string> dataElems = _xpath.evaluate("//" + _nsPrefix + "data", _doc).asNodeSet(); + for (unsigned int i = 0; i < dataElems.size(); i++) { + initializeData(dataElems[i]); + } + } else if(_dataModel) { + NodeSet<std::string> topDataElems = _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "datamodel/" + _nsPrefix + "data", _doc).asNodeSet(); + for (unsigned int i = 0; i < topDataElems.size(); i++) { + initializeData(topDataElems[i]); + } + } + + // executeTransitionContent + Arabica::DOM::Element<std::string> initialState = (Arabica::DOM::Element<std::string>)getInitialState(); + + // create a pseudo initial and transition element + NodeSet<std::string> initialTransitions; + Arabica::DOM::Element<std::string> initialElem = _doc.createElement("initial"); + Arabica::DOM::Element<std::string> transitionElem = _doc.createElement("transition"); + transitionElem.setAttribute("target", initialState.getAttribute("id")); + + initialElem.appendChild(transitionElem); + _scxml.appendChild(initialElem); + + assert(boost::iequals(TAGNAME(initialElem), "initial")); + + initialTransitions.push_back(transitionElem); + enterStates(initialTransitions); + + mainEventLoop(); +} + +void Interpreter::initializeData(const Arabica::DOM::Node<std::string>& data) { + if (!_dataModel) { + LOG(ERROR) << "Cannot initialize data when no datamodel is given!"; + return; + } + try { + if (!HAS_ATTR(data, "id")) + return; + std::string value = "undefined"; + + if (HAS_ATTR(data, "expr")) { + value = ATTR(data, "expr"); + goto VALUE_INITIALIZED; + } + if (HAS_ATTR(data, "src")) { + Arabica::SAX::InputSourceResolver resolver(Arabica::SAX::InputSource<std::string>(ATTR(data, "src")), + Arabica::default_string_adaptor<std::string>()); + value = std::string(std::istreambuf_iterator<char>(*resolver.resolve()), std::istreambuf_iterator<char>()); + goto VALUE_INITIALIZED; + } + if (data.hasChildNodes()) { + // search for the text node with the actual script + if (data.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { + value = data.getFirstChild().getNodeValue(); + } + } + +VALUE_INITIALIZED: + _dataModel->assign(ATTR(data, "id"), value); + + } catch (Event e) { + LOG(ERROR) << "Syntax error in send element:" << std::endl << e << std::endl; + } +} + +void Interpreter::normalize(const Arabica::DOM::Node<std::string>& node) { + // make sure every state has an id and set isFirstEntry to true + Arabica::XPath::NodeSet<std::string> states = _xpath.evaluate("//" + _nsPrefix + "state", _doc).asNodeSet(); + for (int i = 0; i < states.size(); i++) { + Arabica::DOM::Element<std::string> stateElem = Arabica::DOM::Element<std::string>(states[i]); + stateElem.setAttribute("isFirstEntry", "true"); + if (!stateElem.hasAttribute("id")) { + stateElem.setAttribute("id", getUUID()); + } + } + + Arabica::XPath::NodeSet<std::string> finals = _xpath.evaluate("//" + _nsPrefix + "final", _doc).asNodeSet(); + for (int i = 0; i < finals.size(); i++) { + Arabica::DOM::Element<std::string> finalElem = Arabica::DOM::Element<std::string>(finals[i]); + finalElem.setAttribute("isFirstEntry", "true"); + if (!finalElem.hasAttribute("id")) { + finalElem.setAttribute("id", getUUID()); + } + } + + Arabica::XPath::NodeSet<std::string> histories = _xpath.evaluate("//" + _nsPrefix + "history", _doc).asNodeSet(); + for (int i = 0; i < histories.size(); i++) { + Arabica::DOM::Element<std::string> historyElem = Arabica::DOM::Element<std::string>(histories[i]); + if (!historyElem.hasAttribute("id")) { + historyElem.setAttribute("id", getUUID()); + } + } + + Arabica::XPath::NodeSet<std::string> scxml = _xpath.evaluate("/" + _nsPrefix + "scxml", _doc).asNodeSet(); + if (!((Arabica::DOM::Element<std::string>)scxml[0]).hasAttribute("id")) { + ((Arabica::DOM::Element<std::string>)scxml[0]).setAttribute("id", getUUID()); + } +} + +void Interpreter::mainEventLoop() { + while(_running) { + NodeSet<std::string> enabledTransitions; + _stable = false; + + // Here we handle eventless transitions and transitions + // triggered by internal events until machine is stable + while(_running && !_stable) { +#if 0 + std::cout << "Configuration: "; + for (int i = 0; i < _configuration.size(); i++) { + std::cout << ((Arabica::DOM::Element<std::string>)_configuration[i]).getAttribute("id") << ", "; + } + std::cout << std::endl; +#endif + enabledTransitions = selectEventlessTransitions(); + if (enabledTransitions.size() == 0) { + if (_internalQueue.size() == 0) { + _stable = true; + } else { + Event internalEvent = _internalQueue.front(); + _internalQueue.pop_front(); + _dataModel->setEvent(internalEvent); + enabledTransitions = selectTransitions(internalEvent.name); + } + } + if (!enabledTransitions.empty()) + microstep(enabledTransitions); + } + + for (unsigned int i = 0; i < _statesToInvoke.size(); i++) { + NodeSet<std::string> invokes = _xpath.evaluate("" + _nsPrefix + "invoke", _statesToInvoke[i]).asNodeSet(); + for (unsigned int j = 0; j < invokes.size(); j++) { + // @TODO: + // boost::shared_ptr<Invoke> invoke = boost::static_pointer_cast<Invoke>(interpreter::Factory::create("invoke", invokes[j])); + // invoke->invoke(this); + } + } + + _statesToInvoke = NodeSet<std::string>(); + if (!_internalQueue.empty()) + continue; + + { + tthread::lock_guard<tthread::mutex> lock(_mutex); + _stabilized.notify_all(); + } + + Event externalEvent = _externalQueue.pop(); + if (!_running) + exitInterpreter(); + + if (_dataModel && boost::iequals(externalEvent.name, "cancel.invoke." + _sessionId)) + break; + + if (_dataModel) + _dataModel->setEvent(externalEvent); + for (unsigned int i = 0; i < _configuration.size(); i++) { + NodeSet<std::string> invokes = _xpath.evaluate("" + _nsPrefix + "invoke", _configuration[i]).asNodeSet(); + for (unsigned int j = 0; j < invokes.size(); j++) { + std::string invokeId = ((Arabica::DOM::Element<std::string>)invokes[i]).getAttribute("id"); + std::string autoForward = ((Arabica::DOM::Element<std::string>)invokes[i]).getAttribute("autoforward"); + if (boost::iequals(invokeId, externalEvent.invokeid)) { + // @TODO + // boost::shared_ptr<Invoke> invoke = boost::static_pointer_cast<Invoke>(interpreter::Factory::create("invoke", invokes[j])); + // invoke->applyFinalize(this); + } + if (autoForward.length() > 0) { + send(invokeId, externalEvent); + } + } + } + enabledTransitions = selectTransitions(externalEvent.name); + if (!enabledTransitions.empty()) + microstep(enabledTransitions); + } + exitInterpreter(); +} + +void Interpreter::send(const std::string invokeId, Event& event) { +} + +void Interpreter::send(const Arabica::DOM::Node<std::string>& element) { + SendRequest sendReq; + try { + // event + if (HAS_ATTR(element, "eventexpr") && _dataModel) { + sendReq.name = _dataModel->evalAsString(ATTR(element, "eventexpr")); + } else if (HAS_ATTR(element, "event")) { + sendReq.name = ATTR(element, "event"); + } else { + LOG(ERROR) << "send element does not feature event, or eventexpr without datamodel given"; + return; + } + // target + if (HAS_ATTR(element, "targetexpr") && _dataModel) { + sendReq.target = _dataModel->evalAsString(ATTR(element, "targetexpr")); + } else if (HAS_ATTR(element, "target")) { + sendReq.target = ATTR(element, "target"); + } + // type + if (HAS_ATTR(element, "typeexpr") && _dataModel) { + sendReq.type = _dataModel->evalAsString(ATTR(element, "typeexpr")); + } else if (HAS_ATTR(element, "type")) { + sendReq.type = ATTR(element, "type"); + } else { + sendReq.type = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor"; + } + // id + if (HAS_ATTR(element, "idlocation") && _dataModel) { + sendReq.sendid = _dataModel->evalAsString(ATTR(element, "idlocation")); + } else 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. + */ + sendReq.sendid = getUUID(); + } + /** @TODO: + * + * 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. + * + */ + + // delay + std::string delay; + sendReq.delayMs = 0; + if (HAS_ATTR(element, "delayexpr") && _dataModel) { + delay = _dataModel->evalAsString(ATTR(element, "delayexpr")); + } else if (HAS_ATTR(element, "delay")) { + delay = ATTR(element, "delay"); + } + if (delay.size() > 0) { + boost::trim(delay); + std::stringstream delayTime; + if (delay.size() > 2 && boost::iequals("ms", delay.substr(delay.length() - 2, 2))) { + delayTime << delay.substr(0, delay.size() - 2); + delayTime >> sendReq.delayMs; + } else if (delay.size() > 1 && boost::iequals("s", delay.substr(delay.length() - 1, 1))) { + delayTime << delay.substr(0, delay.size() - 1); + delayTime >> sendReq.delayMs; + sendReq.delayMs *= 1000; + } else { + LOG(ERROR) << "Cannot make sense of delay value " << delay << ": does not end in 's' or 'ms'"; + } + } + // namelist + if (HAS_ATTR(element, "namelist")) { + std::vector<std::string> names = tokenizeIdRefs(ATTR(element, "namelist")); + for (int i = 0; i < names.size(); i++) { + sendReq.namelist[names[i]] = _dataModel->evalAsString(names[i]); + } + } + + // params + NodeSet<std::string> params = _xpath.evaluate("" + _nsPrefix + "param", element).asNodeSet(); + for (int i = 0; i < params.size(); i++) { + if (!HAS_ATTR(params[i], "name")) { + LOG(ERROR) << "param element is missing name attribut"; + continue; + } + std::string paramValue; + if (HAS_ATTR(params[i], "expr") && _dataModel) { + std::string location = _dataModel->evalAsString(ATTR(params[i], "expr")); + paramValue = _dataModel->evalAsString(location); + } else if(HAS_ATTR(params[i], "location") && _dataModel) { + paramValue = _dataModel->evalAsString(ATTR(params[i], "location")); + } else { + LOG(ERROR) << "param element is missing expr or location or no datamodel is specified"; + continue; + } + sendReq.params[ATTR(params[i], "name")] = paramValue; + } + + // content + NodeSet<std::string> contents = _xpath.evaluate("" + _nsPrefix + "content", element).asNodeSet(); + if (contents.size() > 1) + LOG(ERROR) << "Only a single content element is allowed for send elements - using first one"; + if (contents.size() > 0) { + if (HAS_ATTR(contents[0], "expr")) { + if (_dataModel) { + sendReq.content = _dataModel->evalAsString(ATTR(contents[0], "expr")); + } else { + LOG(ERROR) << "content element has expr attribute but no datamodel is specified."; + } + } else if (contents[0].hasChildNodes()) { +// dump(contents[0].getFirstChild()); + sendReq.content = contents[0].getFirstChild().getNodeValue(); + } else { + LOG(ERROR) << "content element does not specify any content."; + } + } + + IOProcessor* ioProc = getIOProcessor(sendReq.type); + if (ioProc != NULL) { + _ioProcessorsIds[sendReq.sendid] = ioProc; + ioProc->send(sendReq); + } + + } catch (Event e) { + LOG(ERROR) << "Syntax error in send element:" << std::endl << e << std::endl; + } +} + +void Interpreter::invoke(const Arabica::DOM::Node<std::string>& element) { + InvokeRequest invokeReq; + try { + // type + if (HAS_ATTR(element, "typeexpr") && _dataModel) { + invokeReq.type = _dataModel->evalAsString(ATTR(element, "typeexpr")); + } else if (HAS_ATTR(element, "type")) { + invokeReq.type = ATTR(element, "type"); + } else { + LOG(ERROR) << "invoke element is missing expr or typeexpr or no datamodel is specified"; + } + + // src + if (HAS_ATTR(element, "srcexpr") && _dataModel) { + invokeReq.src = _dataModel->evalAsString(ATTR(element, "srcexpr")); + } else if (HAS_ATTR(element, "src")) { + invokeReq.src = ATTR(element, "src"); + } else { + LOG(ERROR) << "invoke element is missing src or srcexpr or no datamodel is specified"; + } + + // id + if (HAS_ATTR(element, "idlocation") && _dataModel) { + invokeReq.invokeid = _dataModel->evalAsString(ATTR(element, "idlocation")); + } else if (HAS_ATTR(element, "id")) { + invokeReq.invokeid = ATTR(element, "id"); + } else { + invokeReq.invokeid = getUUID(); + } + + // namelist + if (HAS_ATTR(element, "namelist")) { + invokeReq.namelist = ATTR(element, "namelist"); + } + + // autoforward + if (HAS_ATTR(element, "autoforward")) { + if (boost::iequals(ATTR(element, "autoforward"), "true")) { + invokeReq.autoForward = true; + } + } else { + invokeReq.autoForward = false; + } + + // params + NodeSet<std::string> params = _xpath.evaluate("" + _nsPrefix + "param", element).asNodeSet(); + for (int i = 0; i < params.size(); i++) { + if (HAS_ATTR(params[i], "name")) { + LOG(ERROR) << "param element is missing name attribut"; + continue; + } + std::string paramValue; + if (HAS_ATTR(params[i], "expr") && _dataModel) { + paramValue = _dataModel->evalAsString(ATTR(params[i], "expr")); + } else if(HAS_ATTR(params[i], "location") && _dataModel) { + paramValue = _dataModel->evalAsString(ATTR(params[i], "location")); + } else { + LOG(ERROR) << "param element is missing expr or location or no datamodel is specified"; + continue; + } + invokeReq.params[ATTR(params[i], "name")] = paramValue; + } + + // content + NodeSet<std::string> contents = _xpath.evaluate("" + _nsPrefix + "content", element).asNodeSet(); + if (contents.size() > 1) + LOG(ERROR) << "Only a single content element is allowed for send elements - using first one"; + if (contents.size() > 0) { + invokeReq.content = contents[0].getNodeValue(); + } + + IOProcessor* ioProc = getIOProcessor(invokeReq.type); + if (ioProc != NULL) { + _ioProcessorsIds[invokeReq.invokeid] = ioProc; + ioProc->invoke(invokeReq); + } + + } catch (Event e) { + LOG(ERROR) << "Syntax error in invoke element:" << std::endl << e << std::endl; + } + +} + +Arabica::XPath::NodeSet<std::string> Interpreter::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(_configuration[i])) + atomicStates.push_back(_configuration[i]); + } + atomicStates.to_document_order(); + + for (unsigned int i = 0; i < atomicStates.size(); i++) { + NodeSet<std::string> ancestors = getProperAncestors(atomicStates[i], Arabica::DOM::Node<std::string>()); + ancestors.push_back(atomicStates[i]); + for (unsigned int j = 0; j < ancestors.size(); j++) { + NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "transition", ancestors[j]).asNodeSet(); + for (unsigned int k = 0; k < transitions.size(); k++) { + if (((Arabica::DOM::Element<std::string>)transitions[k]).hasAttribute("event") && + nameMatch(((Arabica::DOM::Element<std::string>)transitions[k]).getAttribute("event"), event) && + hasConditionMatch(transitions[k])) { + enabledTransitions.push_back(transitions[k]); + goto LOOP; + } + } + } + LOOP:; + } + return enabledTransitions; +} + +// see: http://www.w3.org/TR/scxml/#EventDescriptors +bool Interpreter::nameMatch(const std::string& transitionEvent, const std::string& event) { + assert(transitionEvent.size() > 0); + assert(event.size() > 0); + + // naive case of single descriptor and exact match + if (boost::equals(transitionEvent, event)) + return true; + + boost::char_separator<char> sep(" "); + boost::tokenizer<boost::char_separator<char> > tokens(transitionEvent, sep); + boost::tokenizer<boost::char_separator<char> >::iterator tokenIter = tokens.begin(); + + while(tokenIter != tokens.end()) { + std::string eventDesc(*tokenIter++); + + // remove optional trailing .* for CCXML compatibility + if (eventDesc.find("*", eventDesc.size() - 1) != std::string::npos) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + if (eventDesc.find(".", eventDesc.size() - 1) != std::string::npos) + eventDesc = eventDesc.substr(0, eventDesc.size() - 1); + + // are they already equal? + if (boost::equals(eventDesc, event)) + return true; + + // eventDesc has to be a real prefix of event now and therefore shorter + if (eventDesc.size() >= event.size()) + continue; + + // it is a prefix of the event name and event continues with .something + if (eventDesc.compare(event.substr(0, eventDesc.size())) == 0) + if (event.find(".", eventDesc.size()) == eventDesc.size()) + return true; + } + return false; +} + +Arabica::XPath::NodeSet<std::string> Interpreter::selectEventlessTransitions() { + Arabica::XPath::NodeSet<std::string> enabledTransitions; + + NodeSet<std::string> atomicStates; + for (unsigned int i = 0; i < _configuration.size(); i++) { + if (isAtomic(_configuration[i])) + atomicStates.push_back(_configuration[i]); + } + atomicStates.to_document_order(); + + for (unsigned int i = 0; i < atomicStates.size(); i++) { + NodeSet<std::string> ancestors = getProperAncestors(atomicStates[i], Arabica::DOM::Node<std::string>()); + ancestors.push_back(atomicStates[i]); + for (unsigned int j = 0; j < ancestors.size(); j++) { + NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "transition", ancestors[j]).asNodeSet(); + for (unsigned int k = 0; k < transitions.size(); k++) { + if (!((Arabica::DOM::Element<std::string>)transitions[k]).hasAttribute("event") && hasConditionMatch(transitions[k])) { + enabledTransitions.push_back(transitions[k]); + goto LOOP; + } + } + } + LOOP:; + } + + enabledTransitions = filterPreempted(enabledTransitions); + return enabledTransitions; +} + +bool Interpreter::hasConditionMatch(const Arabica::DOM::Node<std::string>& conditional) { + try { + if (_dataModel && HAS_ATTR(conditional, "cond")) + return _dataModel->evalAsBool(ATTR(conditional, "cond")); + } catch (Event e) { + LOG(ERROR) << "Syntax error in cond attribute of " << TAGNAME(conditional) << " element:" << std::endl << e << std::endl; + return false; + } + return true; // no condition is always true +} + +Arabica::XPath::NodeSet<std::string> Interpreter::filterPreempted(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { + Arabica::XPath::NodeSet<std::string> filteredTransitions; + for (unsigned int i = 0; i < enabledTransitions.size(); i++) { + Arabica::DOM::Node<std::string> t = enabledTransitions[i]; + for (unsigned int j = i+1; j < enabledTransitions.size(); j++) { + Arabica::DOM::Node<std::string> t2 = enabledTransitions[j]; + if (isPreemptingTransition(t2, t)) + goto LOOP; + } + filteredTransitions.push_back(t); + LOOP:; + } + return filteredTransitions; +} + +bool Interpreter::isPreemptingTransition(const Arabica::DOM::Node<std::string>& t1, const Arabica::DOM::Node<std::string>& t2) { + if (t1 == t2) + return false; + if (isWithinSameChild(t1) && (!isTargetless(t2) && !isWithinSameChild(t2))) + return true; + if (!isTargetless(t1) && !isWithinSameChild(t1)) + return true; + return false; +} + +void Interpreter::microstep(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { +#if 0 + std::cout << "Transitions: "; + for (int i = 0; i < enabledTransitions.size(); i++) { + std::cout << ((Arabica::DOM::Element<std::string>)getSourceState(enabledTransitions[i])).getAttribute("id") << " -> " << std::endl; + NodeSet<std::string> targetSet = getTargetStates(enabledTransitions[i]); + for (int j = 0; j < targetSet.size(); j++) { + std::cout << " " << ((Arabica::DOM::Element<std::string>)targetSet[i]).getAttribute("id") << std::endl; + } + } + std::cout << std::endl; +#endif + + exitStates(enabledTransitions); + executeTransitionContent(enabledTransitions); + enterStates(enabledTransitions); +} + +void Interpreter::exitInterpreter() { + NodeSet<std::string> statesToExit = _configuration; + statesToExit.to_document_order(); + statesToExit.reverse(); + + for (int i = 0; i < statesToExit.size(); i++) { + Arabica::XPath::NodeSet<std::string> onExitElems = _xpath.evaluate("" + _nsPrefix + "onexit", statesToExit[i]).asNodeSet(); + for (int j = 0; j < onExitElems.size(); j++) { + executeContent(onExitElems[j]); + } + Arabica::XPath::NodeSet<std::string> invokeElems = _xpath.evaluate("" + _nsPrefix + "invoke", statesToExit[i]).asNodeSet(); + for (int j = 0; j < invokeElems.size(); j++) { + cancelInvoke(invokeElems[j]); + } + if (isFinal(statesToExit[i]) && parentIsScxmlState(statesToExit[i])) { + returnDoneEvent(statesToExit[i]); + } + } + _configuration = NodeSet<std::string>(); +} + +void Interpreter::executeTransitionContent(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { + for (int i = 0; i < enabledTransitions.size(); i++) { + executeContent(enabledTransitions[i]); + } +} + +void Interpreter::executeContent(const NodeList<std::string>& content) { + for (unsigned int i = 0; i < content.getLength(); i++) { + if (content.item(i).getNodeType() != Node_base::ELEMENT_NODE) + continue; + executeContent(content.item(i)); + } +} + +void Interpreter::executeContent(const Arabica::DOM::Node<std::string>& content) { + if (content.getNodeType() != Node_base::ELEMENT_NODE) + return; + + if (false) { + } else if (boost::iequals(TAGNAME(content), "raise")) { + // --- RAISE -------------------------- + if (HAS_ATTR(content, "event")) { + Event event; + event.name = ATTR(content, "event"); + _internalQueue.push_back(event); + } + } else if (boost::iequals(TAGNAME(content), "if")) { + // --- IF / ELSEIF / ELSE -------------- + Arabica::DOM::Element<std::string> ifElem = (Arabica::DOM::Element<std::string>)content; + if(hasConditionMatch(ifElem)) { + // condition is true, execute all content up to an elseif, else or end + if (ifElem.hasChildNodes()) { + 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; + if (boost::iequals(TAGNAME(childs.item(i)), "elsif") || + boost::iequals(TAGNAME(childs.item(i)), "else")) + break; + executeContent(childs.item(i)); + } + } + } else { + // condition does not match - do we have an elsif? + if (ifElem.hasChildNodes()) { + NodeList<std::string> elseifElem = ifElem.getElementsByTagName("elseif"); + for (unsigned int i = 0; i < elseifElem.getLength(); i++) { + if (hasConditionMatch(elseifElem.item(i))) { + executeContent(elseifElem.item(i).getChildNodes()); + goto ELSIF_ELEM_MATCH; + } + } + NodeList<std::string> elseElem = ifElem.getElementsByTagName("else"); + if (elseElem.getLength() > 0) + executeContent(elseElem.item(0).getChildNodes()); + } + } + ELSIF_ELEM_MATCH:; + } else if (boost::iequals(TAGNAME(content), "elseif")) { + std::cerr << "Found single elsif to evaluate!" << std::endl; + } else if (boost::iequals(TAGNAME(content), "else")) { + std::cerr << "Found single else to evaluate!" << std::endl; + } else if (boost::iequals(TAGNAME(content), "foreach")) { + // --- FOREACH -------------------------- + if (_dataModel) { + 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 = _dataModel->getLength(array); + _dataModel->pushContext(); // copy old and enter new context + for (uint32_t iteration = 0; iteration < iterations; iteration++) { + { + // assign array element to item + std::stringstream ss; + ss << array << "[" << iteration << "]"; + _dataModel->assign(item, ss.str()); + } + if (index.length() > 0) { + // assign iteration element to index + std::stringstream ss; + ss << iteration; + _dataModel->assign(index,ss.str()); + } + if (content.hasChildNodes()) + executeContent(content.getChildNodes()); + } + _dataModel->popContext(); // leave stacked context + } else { + LOG(ERROR) << "Expected array and item attributes with foreach element!" << std::endl; + } + } + } else if (boost::iequals(TAGNAME(content), "log")) { + // --- LOG -------------------------- + Arabica::DOM::Element<std::string> logElem = (Arabica::DOM::Element<std::string>)content; + if (logElem.hasAttribute("expr")) { + if (_dataModel) { + try { + std::cout << _dataModel->evalAsString(logElem.getAttribute("expr")) << std::endl; + } catch (Event e) { + LOG(ERROR) << "Syntax error in expr attribute of log element:" << std::endl << e << std::endl; + } + } else { + std::cout << logElem.getAttribute("expr") << std::endl; + } + } + } else if (boost::iequals(TAGNAME(content), "assign")) { + // --- ASSIGN -------------------------- + if (_dataModel && HAS_ATTR(content, "location") && HAS_ATTR(content, "expr")) { + try { + _dataModel->assign(ATTR(content, "location"), ATTR(content, "expr")); + } catch (Event e) { + LOG(ERROR) << "Syntax error in attributes of assign element:" << std::endl << e << std::endl; + } + } + } else if (boost::iequals(TAGNAME(content), "validate")) { + // --- VALIDATE -------------------------- + if (_dataModel) { + 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 (boost::iequals(TAGNAME(content), "script")) { + // --- SCRIPT -------------------------- + if (_dataModel) { + if (HAS_ATTR(content, "src")) { + Arabica::SAX::InputSourceResolver resolver(Arabica::SAX::InputSource<std::string>(ATTR(content, "src")), + Arabica::default_string_adaptor<std::string>()); + std::string srcContent(std::istreambuf_iterator<char>(*resolver.resolve()), std::istreambuf_iterator<char>()); + try { + _dataModel->eval(srcContent); + } catch (Event e) { + LOG(ERROR) << "Syntax error while executing script element from '" << ATTR(content, "src") << "':" << std::endl << e << std::endl; + } + } else { + if (content.hasChildNodes()) { + // search for the text node with the actual script + if (content.getFirstChild().getNodeType() == Node_base::TEXT_NODE) { + try { + _dataModel->eval(content.getFirstChild().getNodeValue()); + } catch (Event e) { + LOG(ERROR) << "Syntax error while executing script element" << std::endl << e << std::endl; + } + } + } + } + } + } else if (boost::iequals(TAGNAME(content), "send")) { + // --- SEND -------------------------- + send(content); + } else if (boost::iequals(TAGNAME(content), "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; + } + + IOProcessor* ioProc = getIOProcessorForId(sendId); + if (ioProc != NULL) { + ioProc->cancel(sendId); + } + + } catch (Event e) { + LOG(ERROR) << "Syntax error while executing cancel element" << std::endl << e << std::endl; + } + + } else if (boost::iequals(TAGNAME(content), "invoke")) { + // --- INVOKE -------------------------- + } else { + NodeList<std::string> executable = content.getChildNodes(); + for (int i = 0; i < executable.getLength(); i++) { + executeContent(executable.item(i)); + } + } +} + +void Interpreter::cancelInvoke(const Arabica::DOM::Node<std::string>& content) { +} + +void Interpreter::returnDoneEvent(const Arabica::DOM::Node<std::string>& state) { +} + +void Interpreter::exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { + NodeSet<std::string> statesToExit; + for (int i = 0; i < enabledTransitions.size(); i++) { + Arabica::DOM::Element<std::string> transition = ((Arabica::DOM::Element<std::string>)enabledTransitions[i]); + if (!isTargetless(transition)) { + std::string transitionType = (boost::iequals(transition.getAttribute("type"), "internal") ? "internal" : "external"); + NodeSet<std::string> tStates = getTargetStates(transition); + Arabica::DOM::Node<std::string> ancestor; + Arabica::DOM::Node<std::string> source = getSourceState(transition); + + bool allDescendants = true; + for (int j = 0; j < tStates.size(); j++) { + if (!isDescendant(tStates[j], source)) { + allDescendants = false; + break; + } + } + if (boost::iequals(transitionType, "internal") && + isCompound(source) && + allDescendants) + { + ancestor = source; + } else { + NodeSet<std::string> tmpStates; + tmpStates.push_back(source); + tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end()); + + ancestor = findLCCA(tmpStates); + } + + for (int j = 0; j < _configuration.size(); j++) { + if (isDescendant(_configuration[j], ancestor)) + statesToExit.push_back(_configuration[j]); + } + } + } + // remove statesToExit from _statesToInvoke + std::list<Arabica::DOM::Node<std::string> > tmp; + for (int i = 0; i < _statesToInvoke.size(); i++) { + if (!isMember(_statesToInvoke[i], statesToExit)) { + tmp.push_back(_statesToInvoke[i]); + } + } + _statesToInvoke = NodeSet<std::string>(); + _statesToInvoke.insert(_statesToInvoke.end(), tmp.begin(), tmp.end()); + + statesToExit.to_document_order(); + statesToExit.reverse(); + + for (int i = 0; i < statesToExit.size(); i++) { + NodeSet<std::string> historyElems = _xpath.evaluate("" + _nsPrefix + "history", statesToExit[i]).asNodeSet(); + for (int j = 0; j < historyElems.size(); j++) { + Arabica::DOM::Element<std::string> historyElem = (Arabica::DOM::Element<std::string>)historyElems[j]; + std::string historyType = (historyElem.hasAttribute("type") ? historyElem.getAttribute("type") : "shallow"); + NodeSet<std::string> historyNodes; + for (int k = 0; k < _configuration.size(); k++) { + if (boost::iequals(historyType, "deep")) { + if (isAtomic(_configuration[k]) && isDescendant(_configuration[k], statesToExit[i])) + historyNodes.push_back(_configuration[k]); + } else { + if (_configuration[k].getParentNode() == statesToExit[i]) + historyNodes.push_back(_configuration[k]); + } + } + _historyValue[historyElem.getAttribute("id")] = historyNodes; + } + } + + for (int i = 0; i < statesToExit.size(); i++) { + Arabica::XPath::NodeSet<std::string> onExitElems = _xpath.evaluate("" + _nsPrefix + "onexit", statesToExit[i]).asNodeSet(); + for (int j = 0; j < onExitElems.size(); j++) { + executeContent(onExitElems[j]); + } + Arabica::XPath::NodeSet<std::string> invokeElems = _xpath.evaluate("" + _nsPrefix + "invoke", statesToExit[i]).asNodeSet(); + for (int j = 0; j < invokeElems.size(); j++) { + cancelInvoke(invokeElems[j]); + } + } + +// std::cout << "States to Exit: "; +// for (int i = 0; i < statesToExit.size(); i++) { +// std::cout << ((Arabica::DOM::Element<std::string>)statesToExit[i]).getAttribute("id") << ", "; +// } +// std::cout << std::endl; + + // remove statesToExit from _configuration + tmp.clear(); + for (int i = 0; i < _configuration.size(); i++) { + if (!isMember(_configuration[i], statesToExit)) { + tmp.push_back(_configuration[i]); + } + } + _configuration = NodeSet<std::string>(); + _configuration.insert(_configuration.end(), tmp.begin(), tmp.end()); + + +} + +void Interpreter::enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions) { + NodeSet<std::string> statesToEnter; + NodeSet<std::string> statesForDefaultEntry; + + for (int i = 0; i < enabledTransitions.size(); i++) { + Arabica::DOM::Element<std::string> transition = ((Arabica::DOM::Element<std::string>)enabledTransitions[i]); + if (!isTargetless(transition)) { + std::string transitionType = (boost::iequals(transition.getAttribute("type"), "internal") ? "internal" : "external"); + NodeSet<std::string> tStates = getTargetStates(transition); + Arabica::DOM::Node<std::string> ancestor; + Arabica::DOM::Node<std::string> source = getSourceState(transition); + assert(source); + + bool allDescendants = true; + for (int j = 0; j < tStates.size(); j++) { + if (!isDescendant(tStates[j], source)) { + allDescendants = false; + break; + } + } + if (boost::iequals(transitionType, "internal") && + isCompound(source) && + allDescendants) + { + ancestor = source; + } else { + NodeSet<std::string> tmpStates; + tmpStates.push_back(source); + tmpStates.insert(tmpStates.end(), tStates.begin(), tStates.end()); + + ancestor = findLCCA(tmpStates); + } + + for (int j = 0; j < tStates.size(); j++) { + addStatesToEnter(tStates[j], statesToEnter, statesForDefaultEntry); + } + + for (int j = 0; j < tStates.size(); j++) { + NodeSet<std::string> ancestors = getProperAncestors(tStates[j], ancestor); + for (int k = 0; k < ancestors.size(); k++) { + statesToEnter.push_back(ancestors[k]); + if(isParallel(ancestors[k])) { + NodeSet<std::string> childs = getChildStates(ancestors[k]); + for (int l = 0; l < childs.size(); l++) { + bool someIsDescendant = false; + for (int m = 0; m < statesToEnter.size(); m++) { + if (isDescendant(statesToEnter[m], childs[l])) { + someIsDescendant = true; + break; + } + } + if (!someIsDescendant) { + addStatesToEnter(childs[l], statesToEnter, statesForDefaultEntry); + } + } + } + } + } + } + } + statesToEnter.to_document_order(); + for (int i = 0; i < statesToEnter.size(); i++) { + Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)statesToEnter[i]; + _configuration.push_back(stateElem); + _statesToInvoke.push_back(stateElem); + if (_binding == LATE && stateElem.getAttribute("isFirstEntry").size() > 0) { + Arabica::XPath::NodeSet<std::string> dataModelElems = _xpath.evaluate("" + _nsPrefix + "datamodel", stateElem).asNodeSet(); + if(dataModelElems.size() > 0 && _dataModel) { + Arabica::XPath::NodeSet<std::string> dataElems = _xpath.evaluate("" + _nsPrefix + "data", dataModelElems[0]).asNodeSet(); + for (int j = 0; j < dataElems.size(); j++) { + initializeData(dataElems[j]); + } + } + stateElem.setAttribute("isFirstEntry", ""); + } + // execute onentry executable content + Arabica::XPath::NodeSet<std::string> onEntryElems = _xpath.evaluate("" + _nsPrefix + "onentry", stateElem).asNodeSet(); + for (int j = 0; j < onEntryElems.size(); j++) { + executeContent(onEntryElems[j]); + } + if (isMember(stateElem, statesForDefaultEntry)) { + // execute initial transition content for compund states + Arabica::XPath::NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "initial/" + _nsPrefix + "transition", stateElem).asNodeSet(); + for (int j = 0; j < transitions.size(); j++) { + executeContent(transitions[j]); + } + } + + if (isFinal(stateElem)) { + Arabica::DOM::Element<std::string> parent = (Arabica::DOM::Element<std::string>)stateElem.getParentNode(); + + Event event; + event.name = "done.state." + parent.getAttribute("id"); + Arabica::XPath::NodeSet<std::string> doneData = _xpath.evaluate("" + _nsPrefix + "donedata", stateElem).asNodeSet(); + if (doneData.size() > 0) { + event.dom = doneData[0]; + } + _internalQueue.push_back(event); + + if (isParallel(parent.getParentNode())) { + Arabica::DOM::Element<std::string> grandParent = (Arabica::DOM::Element<std::string>)parent.getParentNode(); + + Arabica::XPath::NodeSet<std::string> childs = getChildStates(grandParent); + bool inFinalState = true; + for (int j = 0; j < childs.size(); j++) { + if (!isInFinalState(childs[j])) { + inFinalState = false; + break; + } + } + if (inFinalState) { + Event event; + event.name = "done.state." + grandParent.getAttribute("id"); + _internalQueue.push_back(event); + } + } + } + } + for (int i = 0; i < _configuration.size(); i++) { + Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)_configuration[i]; + if (isFinal(stateElem) && parentIsScxmlState(stateElem)) + _running = false; + } +} + +bool Interpreter::parentIsScxmlState(Arabica::DOM::Node<std::string> state) { + Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)state; + Arabica::DOM::Element<std::string> parentElem = (Arabica::DOM::Element<std::string>)state.getParentNode(); + if (boost::iequals(parentElem.getTagName(), "scxml")) + return true; + return false; +} + +bool Interpreter::isInFinalState(const Arabica::DOM::Node<std::string>& state) { + if (isCompound(state)) { + Arabica::XPath::NodeSet<std::string> childs = getChildStates(state); + for (int i = 0; i < childs.size(); i++) { + if (isFinal(childs[i]) && isMember(childs[i], _configuration)) + return true; + } + } else if (isParallel(state)) { + Arabica::XPath::NodeSet<std::string> childs = getChildStates(state); + for (int i = 0; i < childs.size(); i++) { + if (!isInFinalState(childs[i])) + return false; + } + return true; + } + return false; +} + +bool Interpreter::isMember(const Arabica::DOM::Node<std::string>& node, const Arabica::XPath::NodeSet<std::string>& set) { + for (int i = 0; i < set.size(); i++) { + if (set[i] == node) + return true; + } + return false; +} + +void Interpreter::addStatesToEnter(const Arabica::DOM::Node<std::string>& state, + Arabica::XPath::NodeSet<std::string>& statesToEnter, + Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry) { + std::string stateId = ((Arabica::DOM::Element<std::string>)state).getAttribute("id"); + if (isHistory(state)) { + if (_historyValue.find(stateId) != _historyValue.end()) { + Arabica::XPath::NodeSet<std::string> historyValue = _historyValue[stateId]; + for (int i = 0; i < historyValue.size(); i++) { + addStatesToEnter(historyValue[i], statesToEnter, statesForDefaultEntry); + NodeSet<std::string> ancestors = getProperAncestors(historyValue[i], state); + for (int j = 0; j < ancestors.size(); j++) { + statesToEnter.push_back(ancestors[j]); + } + } + } else { + NodeSet<std::string> transitions = _xpath.evaluate("" + _nsPrefix + "transition", state).asNodeSet(); + for (int i = 0; i < transitions.size(); i++) { + NodeSet<std::string> targets = getTargetStates(transitions[i]); + for (int j = 0; j < targets.size(); j++) { + addStatesToEnter(targets[j], statesToEnter, statesForDefaultEntry); + } + } + } + } else { + statesToEnter.push_back(state); + if (isCompound(state)) { + statesForDefaultEntry.push_back(state); + NodeSet<std::string> tStates = getTargetStates(getInitialState(state)); + for (int i = 0; i < tStates.size(); i++) { + addStatesToEnter(tStates[i], statesToEnter, statesForDefaultEntry); + } + } else if(isParallel(state)) { + NodeSet<std::string> childStates = getChildStates(state); + for (int i = 0; i < childStates.size(); i++) { + addStatesToEnter(childStates[i], statesToEnter, statesForDefaultEntry); + } + } + } +} + +Arabica::XPath::NodeSet<std::string> Interpreter::getChildStates(const Arabica::DOM::Node<std::string>& state) { + Arabica::XPath::NodeSet<std::string> childs; + Arabica::XPath::NodeSet<std::string> stateChilds = _xpath.evaluate("" + _nsPrefix + "state", state).asNodeSet(); + Arabica::XPath::NodeSet<std::string> parallelChilds = _xpath.evaluate("" + _nsPrefix + "parallel", state).asNodeSet(); + + childs.insert(childs.begin(), stateChilds.begin(), stateChilds.end()); + childs.insert(childs.begin(), parallelChilds.begin(), parallelChilds.end()); + + return childs; +} + +Arabica::DOM::Node<std::string> Interpreter::findLCCA(const Arabica::XPath::NodeSet<std::string>& states) { +// std::cout << "findLCCA: "; +// for (int i = 0; i < states.size(); i++) { +// std::cout << ((Arabica::DOM::Element<std::string>)states[i]).getAttribute("id") << " - " << states[i].getLocalName() << ", "; +// } +// std::cout << std::flush; + + Arabica::XPath::NodeSet<std::string> ancestors = getProperAncestors(states[0], Arabica::DOM::Node<std::string>()); + ancestors.push_back(states[0]); // state[0] may already be the ancestor - bug in W3C spec? + Arabica::DOM::Node<std::string> ancestor; + for (int i = 0; i < ancestors.size(); i++) { + for (int j = 0; j < states.size(); j++) { +// std::cout << "Checking " << TAGNAME(state) << " and " << TAGNAME(ancestors[i]) << std::endl; + if (!isDescendant(states[j], ancestors[i]) && (states[j] != ancestors[i])) + goto NEXT_ANCESTOR; + } + ancestor = ancestors[i]; + break; + NEXT_ANCESTOR:; + } + assert(ancestor); +// std::cout << " -> " << ((Arabica::DOM::Element<std::string>)ancestor).getAttribute("id") << " " << ancestor.getLocalName() << std::endl; + return ancestor; +} + +Arabica::DOM::Node<std::string> Interpreter::getState(const std::string& stateId) { + // first try atomic and compund states + NodeSet<std::string> target = _xpath.evaluate("//" + _nsPrefix + "state[@id='" + stateId + "']", _doc).asNodeSet(); + if (target.size() > 0) + goto FOUND; + + // now parallel states + target = _xpath.evaluate("//" + _nsPrefix + "parallel[@id='" + stateId + "']", _doc).asNodeSet(); + if (target.size() > 0) + goto FOUND; + + // now final states + target = _xpath.evaluate("//" + _nsPrefix + "final[@id='" + stateId + "']", _doc).asNodeSet(); + if (target.size() > 0) + goto FOUND; + +FOUND: + if (target.size() > 0) { + assert(target.size() == 1); + return target[0]; + } + // return the empty node + return Arabica::DOM::Node<std::string>(); +} + +Arabica::DOM::Node<std::string> Interpreter::getSourceState(const Arabica::DOM::Node<std::string>& transition) { + if (boost::iequals(TAGNAME(transition.getParentNode()), "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::DOM::Node<std::string> Interpreter::getInitialState(Arabica::DOM::Node<std::string> state) { + if (!state) { + state = _doc.getFirstChild(); + while(!isState(state)) + state = state.getNextSibling(); + } + + assert(isCompound(state) || isParallel(state)); + + // initial attribute at element + Arabica::DOM::Element<std::string> stateElem = (Arabica::DOM::Element<std::string>)state; + if (stateElem.hasAttribute("initial")) { + return getState(stateElem.getAttribute("initial")); + } + + // initial element as child + NodeSet<std::string> initialStates = _xpath.evaluate("" + _nsPrefix + "initial", state).asNodeSet(); + if(initialStates.size() == 1) + return initialStates[0]; + + // first child state + NodeList<std::string> childs = state.getChildNodes(); + for (int i = 0; i < childs.getLength(); i++) { + if (isState(childs.item(i))) + return childs.item(i); + } + // nothing found + return Arabica::DOM::Node<std::string>(); +} + +NodeSet<std::string> Interpreter::getTargetStates(const Arabica::DOM::Node<std::string>& transition) { + NodeSet<std::string> targetStates; + std::string targetId = ((Arabica::DOM::Element<std::string>)transition).getAttribute("target"); + + std::vector<std::string> targetIds = Interpreter::tokenizeIdRefs(ATTR(transition, "target")); + for (int i = 0; i < targetIds.size(); i++) { + targetStates.push_back(getState(targetIds[i])); + } + return targetStates; +} + +std::vector<std::string> Interpreter::tokenizeIdRefs(const std::string& idRefs) { + std::vector<std::string> ids; + + if (idRefs.length() > 0) { + std::istringstream iss(idRefs); + + std::copy(std::istream_iterator<std::string>(iss), + std::istream_iterator<std::string>(), + std::back_inserter<std::vector<std::string> >(ids)); + } + + return ids; +} + +NodeSet<std::string> Interpreter::getProperAncestors(const Arabica::DOM::Node<std::string>& s1, + const Arabica::DOM::Node<std::string>& s2) { + NodeSet<std::string> ancestors; + if (isState(s1)) { + Arabica::DOM::Node<std::string> node = s1; + while((node = node.getParentNode())) { + if (!isState(node)) + break; + if (!boost::iequals(TAGNAME(node), "parallel") && !boost::iequals(TAGNAME(node), "state") && !boost::iequals(TAGNAME(node), "scxml")) + break; + if (node == s2) + break; + ancestors.push_back(node); + } + } + return ancestors; +} + +bool Interpreter::isDescendant(const Arabica::DOM::Node<std::string>& s1, + const Arabica::DOM::Node<std::string>& s2) { + Arabica::DOM::Node<std::string> parent = s1.getParentNode(); + while(parent) { + if (s2 == parent) + return true; + parent = parent.getParentNode(); + } + return false; +} + +bool Interpreter::isTargetless(const Arabica::DOM::Node<std::string>& transition) { + if (transition.hasAttributes()) { + if (((Arabica::DOM::Element<std::string>)transition).hasAttribute("target")) + return false; + } + return true; +} + +bool Interpreter::isWithinSameChild(const Arabica::DOM::Node<std::string>& transition) { + if (!isTargetless(transition)) { + std::string target = ((Arabica::DOM::Element<std::string>)transition).getAttribute("target"); + Arabica::XPath::XPath<std::string> xpath; + // @todo: do we need to look at parallel as well? + if (xpath.evaluate("" + _nsPrefix + "state[id=\"" + target + "\"]", transition.getParentNode()).asNodeSet().size() > 0) + return true; + } + return false; +} + +bool Interpreter::isState(const Arabica::DOM::Node<std::string>& state) { + if (!state) + return false; + if (state.getNodeType() != Arabica::DOM::Node_base::ELEMENT_NODE) + return false; + + std::string tagName = TAGNAME(state); + if (boost::iequals("state", tagName)) + return true; + if (boost::iequals("scxml", tagName)) + return true; + if (boost::iequals("parallel", tagName)) + return true; + if (boost::iequals("final", tagName)) + return true; + return false; +} + +bool Interpreter::isFinal(const Arabica::DOM::Node<std::string>& state) { + std::string tagName = TAGNAME(state); + if (boost::iequals("final", tagName)) + return true; + if (HAS_ATTR(state, "final") && boost::iequals("true", ATTR(state, "final"))) + return true; + return false; +} + +bool Interpreter::isPseudoState(const Arabica::DOM::Node<std::string>& state) { + std::string tagName = TAGNAME(state); + if (boost::iequals("initial", tagName)) + return true; + if (boost::iequals("history", tagName)) + return true; + return false; +} + +bool Interpreter::isTransitionTarget(const Arabica::DOM::Node<std::string>& elem) { + return (isState(elem) || boost::iequals(TAGNAME(elem), "history")); +} + +bool Interpreter::isAtomic(const Arabica::DOM::Node<std::string>& state) { + if (boost::iequals("final", TAGNAME(state))) + return true; + + // I will assume that parallel states are not meant to be atomic. + if (boost::iequals("parallel", TAGNAME(state))) + return false; + + Arabica::DOM::NodeList<std::string> childs = state.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + if (isState(childs.item(i))) + return false; + } + return true; +} + +bool Interpreter::isHistory(const Arabica::DOM::Node<std::string>& state) { + if (boost::iequals("history", TAGNAME(state))) + return true; + return false; +} + +bool Interpreter::isParallel(const Arabica::DOM::Node<std::string>& state) { + if (!isState(state)) + return false; + if (boost::iequals("parallel", TAGNAME(state))) + return true; + return false; +} + + +bool Interpreter::isCompound(const Arabica::DOM::Node<std::string>& state) { + if (!isState(state)) + return false; + + if (boost::iequals(TAGNAME(state), "parallel")) + return false; + + Arabica::DOM::NodeList<std::string> childs = state.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + if (isState(childs.item(i))) + return true; + } + return false; +} + +void Interpreter::setupIOProcessors() { + std::map<std::string, IOProcessor*>::iterator ioProcIter = Factory::getInstance()->_ioProcessors.begin(); + while(ioProcIter != Factory::getInstance()->_ioProcessors.end()) { + _ioProcessors[ioProcIter->first] = Factory::getIOProcessor(ioProcIter->first, this); + if (_dataModel) { + try { + _dataModel->assign("_ioprocessors['" + ioProcIter->first + "']", "'" + _ioProcessors[ioProcIter->first]->getURL() + "'"); + } catch (Event e) { + LOG(ERROR) << "Syntax error when setting _ioprocessors:" << std::endl << e << std::endl; + } + } else { + LOG(INFO) << "Not registering " << ioProcIter->first << " at _ioprocessors in datamodel, no datamodel specified"; + } + ioProcIter++; + } +} + +IOProcessor* Interpreter::getIOProcessor(const std::string& type) { + if (_ioProcessors.find(type) == _ioProcessors.end()) { + LOG(ERROR) << "No ioProcessor known for type " << type; + return NULL; + } + return _ioProcessors[type]; +} + +IOProcessor* Interpreter::getIOProcessorForId(const std::string& sendId) { + if (_ioProcessorsIds.find(sendId) == _ioProcessorsIds.end()) { + LOG(ERROR) << "No ioProcessor with pending send id " << sendId << sendId; + return NULL; + } + return _ioProcessorsIds[sendId]; +} + +bool Interpreter::validate() { + bool validationErrors = false; + + if (!_doc) { + LOG(ERROR) << "Document " << _url << " was not parsed successfully" << std::endl; + return false; + } + + // semantic issues ------------ + if ((_xpath.evaluate("/" + _nsPrefix + "scxml/@datamodel", _doc).asNodeSet().size() == 0) && + _xpath.evaluate("/" + _nsPrefix + "scxml/" + _nsPrefix + "script", _doc).asNodeSet().size() > 0) { + LOG(ERROR) << "Script elements used, but no datamodel specified" << std::endl; + } + + // element issues ------------ + Arabica::XPath::NodeSet<std::string> scxmlElems = _xpath.evaluate(_nsPrefix + "scxml", _doc).asNodeSet(); + if (scxmlElems.size() > 0) + LOG(ERROR) << "More than one scxml element found" << std::endl; + for (unsigned int i = 0; i < scxmlElems.size(); i++) { + if (!HAS_ATTR(scxmlElems[i], "xmlns")) + LOG(ERROR) << "scxml element has no xmlns attribute" << std::endl; + if (!HAS_ATTR(scxmlElems[i], "version")) + LOG(ERROR) << "scxml element has no version attribute" << std::endl; + NodeList<std::string> childs = scxmlElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "state") || + boost::iequals(TAGNAME(childs.item(j)), "parallel") || + boost::iequals(TAGNAME(childs.item(j)), "final") || + boost::iequals(TAGNAME(childs.item(j)), "datamodel") || + boost::iequals(TAGNAME(childs.item(j)), "script")) { + LOG(ERROR) << "scxml element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet<std::string> stateElems = _xpath.evaluate(_nsPrefix + "state", _doc).asNodeSet(); + for (unsigned int i = 0; i < stateElems.size(); i++) { + NodeList<std::string> childs = stateElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "onentry") || + boost::iequals(TAGNAME(childs.item(j)), "onexit") || + boost::iequals(TAGNAME(childs.item(j)), "transition") || + boost::iequals(TAGNAME(childs.item(j)), "initial") || + boost::iequals(TAGNAME(childs.item(j)), "state") || + boost::iequals(TAGNAME(childs.item(j)), "parallel") || + boost::iequals(TAGNAME(childs.item(j)), "final") || + boost::iequals(TAGNAME(childs.item(j)), "history") || + boost::iequals(TAGNAME(childs.item(j)), "datamodel") || + boost::iequals(TAGNAME(childs.item(j)), "invoke")) { + LOG(ERROR) << "state element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet<std::string> parallelElems = _xpath.evaluate(_nsPrefix + "parallel", _doc).asNodeSet(); + for (unsigned int i = 0; i < parallelElems.size(); i++) { + NodeList<std::string> childs = parallelElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "onentry") || + boost::iequals(TAGNAME(childs.item(j)), "onexit") || + boost::iequals(TAGNAME(childs.item(j)), "transition") || + boost::iequals(TAGNAME(childs.item(j)), "state") || + boost::iequals(TAGNAME(childs.item(j)), "parallel") || + boost::iequals(TAGNAME(childs.item(j)), "history") || + boost::iequals(TAGNAME(childs.item(j)), "datamodel") || + boost::iequals(TAGNAME(childs.item(j)), "invoke")) { + LOG(ERROR) << "parallel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet<std::string> transitionElems = _xpath.evaluate(_nsPrefix + "transition", _doc).asNodeSet(); + for (unsigned int i = 0; i < transitionElems.size(); i++) { + if (HAS_ATTR(transitionElems[i], "cond") && + !HAS_ATTR(transitionElems[i], "event")) { + LOG(ERROR) << "transition element with cond attribute but without event attribute not allowed" << std::endl; + } + NodeList<std::string> childs = scxmlElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + } + } + + Arabica::XPath::NodeSet<std::string> initialElems = _xpath.evaluate(_nsPrefix + "initial", _doc).asNodeSet(); + for (unsigned int i = 0; i < initialElems.size(); i++) { + NodeList<std::string> childs = initialElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "transition")) { + LOG(ERROR) << "initial element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet<std::string> finalElems = _xpath.evaluate(_nsPrefix + "final", _doc).asNodeSet(); + for (unsigned int i = 0; i < finalElems.size(); i++) { + NodeList<std::string> childs = finalElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (!boost::iequals(TAGNAME(childs.item(j)), "onentry") || + !boost::iequals(TAGNAME(childs.item(j)), "onexit") || + !boost::iequals(TAGNAME(childs.item(j)), "donedata")) { + LOG(ERROR) << "parallel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet<std::string> historyElems = _xpath.evaluate(_nsPrefix + "history", _doc).asNodeSet(); + for (unsigned int i = 0; i < historyElems.size(); i++) { + NodeList<std::string> childs = historyElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (boost::iequals(TAGNAME(childs.item(j)), "transition")) { + LOG(ERROR) << "history element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet<std::string> datamodelElems = _xpath.evaluate(_nsPrefix + "datamodel", _doc).asNodeSet(); + for (unsigned int i = 0; i < datamodelElems.size(); i++) { + NodeList<std::string> childs = datamodelElems[i].getChildNodes(); + for (unsigned int j = 0; j < childs.getLength(); j++) { + if (childs.item(j).getNodeType() != Node_base::ELEMENT_NODE) + continue; + if (!boost::iequals(TAGNAME(childs.item(j)), "data")) { + LOG(ERROR) << "datamodel element has unspecified child " << TAGNAME(childs.item(j)) << std::endl; + } + } + } + + Arabica::XPath::NodeSet<std::string> dataElems = _xpath.evaluate(_nsPrefix + "data", _doc).asNodeSet(); + for (unsigned int i = 0; i < dataElems.size(); i++) { + if (!HAS_ATTR(dataElems[i], "id")) + LOG(ERROR) << "data element has no id attribute" << std::endl; + } + + return validationErrors; +} + +void Interpreter::dump() { + if (!_doc) + return; + dump(_doc); +} + +void Interpreter::dump(const Arabica::DOM::Node<std::string>& node, int lvl) { + if (!node) + return; + + std::string indent = ""; + for (unsigned int i = 0; i < lvl; i++) + indent += " "; + + std::cout << indent; + switch(node.getNodeType()) { + case Arabica::DOM::Node_base::ELEMENT_NODE: { + std::cout << "ELEMENT_NODE: "; + Arabica::DOM::Element<std::string> elem = (Arabica::DOM::Element<std::string>)node; + break; + } + case Arabica::DOM::Node_base::ATTRIBUTE_NODE: + std::cout << "ATTRIBUTE_NODE: "; + break; + case Arabica::DOM::Node_base::TEXT_NODE: + std::cout << "TEXT_NODE: "; + break; + case Arabica::DOM::Node_base::CDATA_SECTION_NODE: + std::cout << "CDATA_SECTION_NODE: "; + break; + case Arabica::DOM::Node_base::ENTITY_REFERENCE_NODE: + std::cout << "ENTITY_REFERENCE_NODE: "; + break; + case Arabica::DOM::Node_base::ENTITY_NODE: + std::cout << "ENTITY_NODE: "; + break; + case Arabica::DOM::Node_base::PROCESSING_INSTRUCTION_NODE: + std::cout << "PROCESSING_INSTRUCTION_NODE: "; + break; + case Arabica::DOM::Node_base::COMMENT_NODE: + std::cout << "COMMENT_NODE: "; + break; + case Arabica::DOM::Node_base::DOCUMENT_NODE: + std::cout << "DOCUMENT_NODE: "; + break; + case Arabica::DOM::Node_base::DOCUMENT_TYPE_NODE: + std::cout << "DOCUMENT_TYPE_NODE: "; + break; + case Arabica::DOM::Node_base::DOCUMENT_FRAGMENT_NODE: + std::cout << "DOCUMENT_FRAGMENT_NODE: "; + break; + case Arabica::DOM::Node_base::NOTATION_NODE: + std::cout << "NOTATION_NODE: "; + break; + case Arabica::DOM::Node_base::MAX_TYPE: + std::cout << "MAX_TYPE: "; + break; + } + std::cout << node.getNamespaceURI() << " " << node.getNodeName() << std::endl; + + if (node.getNodeValue().length() > 0 && node.getNodeValue().find_first_not_of(" \t\n") != std::string::npos) + std::cout << indent << "Value: '" << node.getNodeValue() << "'" << std::endl; + + + if (node.hasAttributes()) { + Arabica::DOM::NamedNodeMap<std::string> attrs = node.getAttributes(); + for (unsigned int i = 0; i < attrs.getLength(); i++) { + std::cout << indent << " " << attrs.item(i).getLocalName() << " = " << attrs.item(i).getNodeValue() << " (" << std::endl; + std::cout << indent << " namespace: " << attrs.item(i).getNamespaceURI() << std::endl; + std::cout << indent << " nodeName: " << attrs.item(i).getNodeName() << std::endl; + std::cout << indent << " prefix: " << attrs.item(i).getPrefix() << std::endl; + std::cout << indent << " )" << std::endl; + } + } + + if (node.hasChildNodes()) { + Arabica::DOM::NodeList<std::string> childs = node.getChildNodes(); + for (unsigned int i = 0; i < childs.getLength(); i++) { + dump(childs.item(i), lvl+1); + } + } +} + +}
\ No newline at end of file diff --git a/src/uscxml/Interpreter.h b/src/uscxml/Interpreter.h new file mode 100644 index 0000000..fb913d1 --- /dev/null +++ b/src/uscxml/Interpreter.h @@ -0,0 +1,314 @@ +#ifndef RUNTIME_H_SQ1MBKGN +#define RUNTIME_H_SQ1MBKGN + +#include <boost/uuid/uuid_generators.hpp> +#include <boost/algorithm/string.hpp> + +#include <iostream> +#include <set> +#include <map> + +#include <XPath/XPath.hpp> +#include <DOM/Document.hpp> + +#include <DOM/SAX2DOM/SAX2DOM.hpp> +#include <SAX/helpers/CatchErrorHandler.hpp> + +#include "uscxml/concurrency/tinythread.h" +#include "uscxml/concurrency/BlockingQueue.h" +#include "uscxml/Message.h" +#include "uscxml/Factory.h" + +namespace uscxml { + + class Interpreter { + public: + enum Binding { + EARLY = 0, + LATE = 1 + }; + + Interpreter(const std::string& url); + virtual ~Interpreter(); + + void start(); + void stop(); + static void run(void*); + + void interpret(); + bool validate(); + + void waitForStabilization(); + + void receive(Event& event) { _externalQueue.push(event); } + void receiveInternal(Event& event) { _internalQueue.push_back(event); } + Arabica::XPath::NodeSet<std::string> getConfiguration() { return _configuration; } + Arabica::DOM::Node<std::string> getState(const std::string& stateId); + + const std::string& getName() { return _name; } + const std::string& getSessionId() { return _sessionId; } + + static bool isMember(const Arabica::DOM::Node<std::string>& node, const Arabica::XPath::NodeSet<std::string>& set); + + void dump(); + static void dump(const Arabica::DOM::Node<std::string>& node, int lvl = 0); + + protected: + void normalize(const Arabica::DOM::Node<std::string>& node); + void setupIOProcessors(); + + void mainEventLoop(); + + bool _stable; + tthread::thread* _thread; + tthread::mutex _mutex; + tthread::condition_variable _stabilized; + + std::string _url; + Arabica::DOM::Document<std::string> _doc; + Arabica::DOM::Element<std::string> _scxml; + Arabica::XPath::XPath<std::string> _xpath; + Arabica::XPath::StandardNamespaceContext<std::string> _nsContext; + std::string _nsPrefix; + + bool _running; + Binding _binding; + Arabica::XPath::NodeSet<std::string> _configuration; + Arabica::XPath::NodeSet<std::string> _statesToInvoke; + + DataModel* _dataModel; + std::map<std::string, Arabica::XPath::NodeSet<std::string> > _historyValue; + + std::list<Event > _internalQueue; + uscxml::concurrency::BlockingQueue<Event> _externalQueue; + + void microstep(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); + void exitStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); + void enterStates(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); + void executeTransitionContent(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); + void executeContent(const Arabica::DOM::Node<std::string>& content); + void executeContent(const Arabica::DOM::NodeList<std::string>& content); + void initializeData(const Arabica::DOM::Node<std::string>& data); + void exitInterpreter(); + + void addStatesToEnter(const Arabica::DOM::Node<std::string>& state, + Arabica::XPath::NodeSet<std::string>& statesToEnter, + Arabica::XPath::NodeSet<std::string>& statesForDefaultEntry); + + Arabica::XPath::NodeSet<std::string> selectEventlessTransitions(); + Arabica::XPath::NodeSet<std::string> selectTransitions(const std::string& event); + Arabica::XPath::NodeSet<std::string> getTargetStates(const Arabica::DOM::Node<std::string>& transition); + Arabica::XPath::NodeSet<std::string> getChildStates(const Arabica::DOM::Node<std::string>& state); + Arabica::DOM::Node<std::string> getInitialState(Arabica::DOM::Node<std::string> state = Arabica::DOM::Node<std::string>()); + Arabica::DOM::Node<std::string> getSourceState(const Arabica::DOM::Node<std::string>& transition); + Arabica::DOM::Node<std::string> findLCCA(const Arabica::XPath::NodeSet<std::string>& states); + static Arabica::XPath::NodeSet<std::string> getProperAncestors(const Arabica::DOM::Node<std::string>& s1, const Arabica::DOM::Node<std::string>& s2); + + + void send(const std::string invokeId, Event& event); + void send(const Arabica::DOM::Node<std::string>& element); + void invoke(const Arabica::DOM::Node<std::string>& element); + void cancelInvoke(const Arabica::DOM::Node<std::string>& content); + void returnDoneEvent(const Arabica::DOM::Node<std::string>& state); + + static bool nameMatch(const std::string& transitionEvent, const std::string& event); + Arabica::XPath::NodeSet<std::string> filterPreempted(const Arabica::XPath::NodeSet<std::string>& enabledTransitions); + bool hasConditionMatch(const Arabica::DOM::Node<std::string>& conditional); + bool isPreemptingTransition(const Arabica::DOM::Node<std::string>& t1, const Arabica::DOM::Node<std::string>& t2); + bool isInFinalState(const Arabica::DOM::Node<std::string>& state); + bool isWithinSameChild(const Arabica::DOM::Node<std::string>& transition); + bool parentIsScxmlState(Arabica::DOM::Node<std::string> state); + + static bool isState(const Arabica::DOM::Node<std::string>& state); + static bool isPseudoState(const Arabica::DOM::Node<std::string>& state); + static bool isTransitionTarget(const Arabica::DOM::Node<std::string>& elem); + static bool isTargetless(const Arabica::DOM::Node<std::string>& transition); + static bool isAtomic(const Arabica::DOM::Node<std::string>& state); + static bool isFinal(const Arabica::DOM::Node<std::string>& state); + static bool isHistory(const Arabica::DOM::Node<std::string>& state); + static bool isParallel(const Arabica::DOM::Node<std::string>& state); + static bool isCompound(const Arabica::DOM::Node<std::string>& state); + static bool isDescendant(const Arabica::DOM::Node<std::string>& s1, const Arabica::DOM::Node<std::string>& s2); + + static std::vector<std::string> tokenizeIdRefs(const std::string& idRefs); + + static boost::uuids::random_generator uuidGen; + static const std::string getUUID(); + + std::string _name; + std::string _sessionId; + + IOProcessor* getIOProcessor(const std::string& type); + IOProcessor* getIOProcessorForId(const std::string& sendId); + + std::map<std::string, IOProcessor*> _ioProcessorsIds; + std::map<std::string, IOProcessor*> _ioProcessors; + + }; + +#if 0 + class SCXMLParseHandler : + public Arabica::SAX::EntityResolver<std::string>, + public Arabica::SAX::DTDHandler<std::string>, + public Arabica::SAX::ContentHandler<std::string>, + public Arabica::SAX::CatchErrorHandler<std::string>, + public Arabica::SAX::LexicalHandler<std::string>, + public Arabica::SAX::DeclHandler<std::string> + { + public: + SCXMLParseHandler() { } + virtual ~SCXMLParseHandler() { } + + // EntityResolver + virtual InputSourceT resolveEntity(const std::string& publicId , const std::string& systemId) { + return InputSourceT(); + } + + // DTDHandler + virtual void notationDecl(const std::string& name, + const std::string& publicId, + const std::string& systemId) { + std::cout << "notationDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + } + virtual void unparsedEntityDecl(const std::string& name, + const std::string& publicId, + const std::string& systemId, + const std::string& notationName) { + std::cout << "unparsedEntityDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + std::cout << " notationName:" << notationName << std::endl; + } + + // ContentHandler + virtual void setDocumentLocator(const LocatorT& locator) { + std::cout << "setDocumentLocator" << std::endl; + } + virtual void startDocument() { + std::cout << "startDocument" << std::endl; + } + virtual void endDocument() { + std::cout << "endDocument" << std::endl; + } + virtual void startPrefixMapping(const std::string& prefix, const std::string& uri) { + std::cout << "startPrefixMapping" << std::endl; + std::cout << " prefix:" << prefix << std::endl; + std::cout << " uri:" << uri << std::endl; + } + virtual void endPrefixMapping(const std::string& prefix) { + std::cout << "endPrefixMapping" << std::endl; + std::cout << " prefix:" << prefix << std::endl; + } + virtual void startElement(const std::string& namespaceURI, const std::string& localName, + const std::string& qName, const AttributesT& atts) { + std::cout << "startElement" << std::endl; + std::cout << " namespaceURI:" << namespaceURI << std::endl; + std::cout << " localName:" << localName << std::endl; + std::cout << " qName:" << qName << std::endl; + std::cout << " atts:" << atts.getLength() << std::endl; + } + virtual void endElement(const std::string& namespaceURI, const std::string& localName, + const std::string& qName) { + std::cout << "endElement" << std::endl; + std::cout << " namespaceURI:" << namespaceURI << std::endl; + std::cout << " localName:" << localName << std::endl; + std::cout << " qName:" << qName << std::endl; + } + virtual void characters(const std::string& ch) { + std::cout << "characters" << std::endl; + std::cout << " ch:" << ch << std::endl; + } + virtual void ignorableWhitespace(const std::string& ch) { + std::cout << "ignorableWhitespace" << std::endl; + std::cout << " ch:" << ch << std::endl; + } + virtual void processingInstruction(const std::string& target, const std::string& data) { + std::cout << "processingInstruction" << std::endl; + std::cout << " target:" << target << std::endl; + std::cout << " data:" << data << std::endl; + } + virtual void skippedEntity(const std::string& name) { + std::cout << "skippedEntity" << std::endl; + std::cout << " name:" << name << std::endl; + } + + // ErrorHandler + virtual void warning(const SAXParseExceptionT& e) { Arabica::SAX::CatchErrorHandler<std::string>::warning(e); } + virtual void error(const SAXParseExceptionT& e) { Arabica::SAX::CatchErrorHandler<std::string>::error(e); } + virtual void fatalError(const SAXParseExceptionT& e) { + Arabica::SAX::CatchErrorHandler<std::string>::fatalError(e); + } + + // LexicalHandler + virtual void startDTD(const std::string& name, + const std::string& publicId, + const std::string& systemId) { + std::cout << "startDTD" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + } + + virtual void endDTD() { + std::cout << "endDTD" << std::endl; + } + virtual void startEntity(const std::string& name) { + std::cout << "startEntity" << std::endl; + std::cout << " name:" << name << std::endl; + } + virtual void endEntity(const std::string& name) { + std::cout << "endEntity" << std::endl; + std::cout << " name:" << name << std::endl; + } + virtual void startCDATA() { + std::cout << "startCDATA" << std::endl; + } + virtual void endCDATA() { + std::cout << "endCDATA" << std::endl; + } + virtual void comment(const std::string& text) { + std::cout << "comment" << std::endl; + std::cout << " text:" << text << std::endl; + } + + // DeclHandler + virtual void elementDecl(const std::string& name, const std::string& model) { + std::cout << "elementDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " model:" << model << std::endl; + } + virtual void attributeDecl(const std::string& elementName, + const std::string& attributeName, + const std::string& type, + const std::string& valueDefault, + const std::string& value) { + std::cout << "attributeDecl" << std::endl; + std::cout << " elementName:" << elementName << std::endl; + std::cout << " attributeName:" << attributeName << std::endl; + std::cout << " type:" << type << std::endl; + std::cout << " valueDefault:" << valueDefault << std::endl; + std::cout << " value:" << value << std::endl; + } + virtual void internalEntityDecl(const std::string& name, const std::string& value) { + std::cout << "internalEntityDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " value:" << value << std::endl; + } + virtual void externalEntityDecl(const std::string& name, + const std::string& publicId, + const std::string& systemId) { + std::cout << "externalEntityDecl" << std::endl; + std::cout << " name:" << name << std::endl; + std::cout << " publicId:" << publicId << std::endl; + std::cout << " systemId:" << systemId << std::endl; + } + + }; +#endif +} + +#endif diff --git a/src/uscxml/Message.cpp b/src/uscxml/Message.cpp new file mode 100644 index 0000000..3d3048b --- /dev/null +++ b/src/uscxml/Message.cpp @@ -0,0 +1,168 @@ +#include "uscxml/Message.h" +#include <DOM/SAX2DOM/SAX2DOM.hpp> +#include <SAX/helpers/CatchErrorHandler.hpp> + +namespace uscxml { + +static int _dataIndentation = 1; + + +Arabica::DOM::Document<std::string> Data::toDocument() { + Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation(); + Arabica::DOM::Document<std::string> document = domFactory.createDocument("http://www.w3.org/2005/07/scxml", "message", 0); + Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement(); + scxmlMsg.setPrefix("scxml"); + scxmlMsg.setAttribute("version", "1.0"); + + if (compound.size() > 0 || array.size() > 0) { + Arabica::DOM::Element<std::string> payloadElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:payload"); + scxmlMsg.appendChild(payloadElem); + + // we do not support nested attibutes + if (compound.size() > 0) { + std::map<std::string, Data>::iterator compoundIter = compound.begin(); + while(compoundIter != compound.end()) { + if (compoundIter->second.atom.size() > 0) { + Arabica::DOM::Element<std::string> propertyElem = document.createElementNS("http://www.w3.org/2005/07/scxml", "scxml:property"); + propertyElem.setAttribute("name", compoundIter->first); + Arabica::DOM::Text<std::string> textElem = document.createTextNode(compoundIter->second.atom); + propertyElem.appendChild(textElem); + payloadElem.appendChild(propertyElem); + } + compoundIter++; + } + } + } + return document; +} + +Arabica::DOM::Document<std::string> Event::toDocument() { + Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation(); + Arabica::DOM::Document<std::string> document = Data::toDocument(); + Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement(); + + scxmlMsg.setAttribute("source", origin); + scxmlMsg.setAttribute("name", name); + + return document; +} + +Arabica::DOM::Document<std::string> SendRequest::toDocument() { + Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation(); + Arabica::DOM::Document<std::string> document = Event::toDocument(); + Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement(); + + scxmlMsg.setAttribute("sendid", sendid); + + return document; +} + +Arabica::DOM::Document<std::string> InvokeRequest::toDocument() { + Arabica::DOM::DOMImplementation<std::string> domFactory = Arabica::SimpleDOM::DOMImplementation<std::string>::getDOMImplementation(); + Arabica::DOM::Document<std::string> document = Event::toDocument(); + Arabica::DOM::Element<std::string> scxmlMsg = document.getDocumentElement(); + + scxmlMsg.setAttribute("invokeid", invokeid); + + return document; +} + +Data Data::fromXML(const std::string& xmlString) { +} + +Event Event::fromXML(const std::string& xmlString) { + Arabica::SAX2DOM::Parser<std::string> eventParser; + Arabica::SAX::CatchErrorHandler<std::string> errorHandler; + eventParser.setErrorHandler(errorHandler); + + std::istringstream is(xmlString); + Arabica::SAX::InputSource<std::string> inputSource; + inputSource.setByteStream(is); + + Event event; + if(eventParser.parse(inputSource) && eventParser.getDocument().hasChildNodes()) { + Arabica::DOM::Element<std::string> scxmlMsg = eventParser.getDocument().getDocumentElement(); + if (HAS_ATTR(scxmlMsg, "name")) + event.name = ATTR(scxmlMsg, "name"); + if (HAS_ATTR(scxmlMsg, "sendid")) + event.sendid = ATTR(scxmlMsg, "sendid"); + } + return event; +} + +SendRequest SendRequest::fromXML(const std::string& xmlString) { + Event::fromXML(xmlString); +} + +InvokeRequest InvokeRequest::fromXML(const std::string& xmlString) { + Event::fromXML(xmlString); +} + +#ifndef SWIGJAVA +std::ostream& operator<< (std::ostream& os, const Event& event) { + os << (event.type == Event::EXTERNAL ? "External" : "Internal") << " Event " << (event.dom ? "with DOM attached" : "") << std::endl; + + if (event.name.size() > 0) + os << " name: " << event.name << std::endl; + if (event.origin.size() > 0) + os << " origin: " << event.origin << std::endl; + if (event.origintype.size() > 0) + os << " origintype: " << event.origintype << std::endl; + _dataIndentation++; + os << " data: " << (Data)event << std::endl; + _dataIndentation--; + return os; +} +#endif + +#ifndef SWIGJAVA +std::ostream& operator<< (std::ostream& os, const Data& data) { + std::string indent; + for (int i = 0; i < _dataIndentation; i++) { + indent += " "; + } + if (false) { + } else if (data.compound.size() > 0) { + int longestKey = 0; + std::map<std::string, Data>::const_iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + if (compoundIter->first.size() > longestKey) + longestKey = compoundIter->first.size(); + compoundIter++; + } + std::string keyPadding; + for (unsigned int i = 0; i < longestKey; i++) + keyPadding += " "; + + os << "{" << std::endl; + compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + os << indent << " \"" << compoundIter->first << "\" " << keyPadding.substr(0, longestKey - compoundIter->first.size()) << "= "; + _dataIndentation += 2; + os << compoundIter->second << "," << std::endl; + _dataIndentation -= 2; + compoundIter++; + } + os << indent << "}" << std::endl; + } else if (data.array.size() > 0) { + os << "[" << std::endl; + std::map<std::string, Data>::const_iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + _dataIndentation += 2; + os << indent << " " << compoundIter->second << "," << std::endl; + _dataIndentation -= 2; + compoundIter++; + } + os << indent << "]" << std::endl; + } else if (data.atom.size() > 0) { + if (data.type == Data::VERBATIM) { + os << indent << "\"" << data.atom << "\""; + } else { + os << indent << data.atom; + } + } + return os; +} +#endif + +}
\ No newline at end of file diff --git a/src/uscxml/Message.h b/src/uscxml/Message.h new file mode 100644 index 0000000..a520ff5 --- /dev/null +++ b/src/uscxml/Message.h @@ -0,0 +1,122 @@ +#ifndef EVENT_H_XZAQ4HR +#define EVENT_H_XZAQ4HR + +#include <map> +#include <vector> +#include <string> + +#include <DOM/Document.hpp> +#include <DOM/io/Stream.hpp> + +#define TAGNAME(elem) ((Arabica::DOM::Element<std::string>)elem).getTagName() +#define ATTR(elem, attr) ((Arabica::DOM::Element<std::string>)elem).getAttribute(attr) +#define HAS_ATTR(elem, attr) ((Arabica::DOM::Element<std::string>)elem).hasAttribute(attr) + +namespace uscxml { + +class Data { +public: + enum Type { + VERBATIM, + INTERPRETED + }; + + Data() {} + Data(const std::string& atom_, Type type_ = INTERPRETED) : atom(atom_), type(type_) {} + virtual ~Data() {} + + static Data fromXML(const std::string& xmlString); + Arabica::DOM::Document<std::string> toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + + std::map<std::string, Data> compound; + std::vector<Data> array; + std::string atom; + Type type; + +protected: + Arabica::DOM::Document<std::string> toNode(const Arabica::DOM::Document<std::string>& factory, const Data& data); + +#ifndef SWIGJAVA + friend std::ostream& operator<< (std::ostream& os, const Data& data); +#endif +}; + +class Event : public Data { +public: + enum Type { + PLATFORM, + INTERNAL, + EXTERNAL + }; + + Event() : type(INTERNAL) {} + + std::string name; + Type type; + std::string origin; + std::string origintype; + Arabica::DOM::Node<std::string> dom; + std::string sendid; + std::string invokeid; + + static Event fromXML(const std::string& xmlString); + Arabica::DOM::Document<std::string> toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + +#ifndef SWIGJAVA + friend std::ostream& operator<< (std::ostream& os, const Event& event); +#endif +}; + +class InvokeRequest : public Event { +public: + std::string type; + std::string src; + std::string namelist; + bool autoForward; + Arabica::DOM::Node<std::string> finalize; + std::map<std::string, std::string> params; + std::string content; + + static InvokeRequest fromXML(const std::string& xmlString); + Arabica::DOM::Document<std::string> toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + +}; + +class SendRequest : public Event { +public: + std::string target; + std::string type; + uint32_t delayMs; + std::map<std::string, std::string> params; + std::map<std::string, std::string> namelist; + std::string content; + + static SendRequest fromXML(const std::string& xmlString); + Arabica::DOM::Document<std::string> toDocument(); + std::string toXMLString() { + std::stringstream ss; + ss << toDocument(); + return ss.str(); + } + +}; + +} + + +#endif /* end of include guard: EVENT_H_XZAQ4HR */ diff --git a/src/uscxml/concurrency/BlockingQueue.h b/src/uscxml/concurrency/BlockingQueue.h new file mode 100644 index 0000000..90094bf --- /dev/null +++ b/src/uscxml/concurrency/BlockingQueue.h @@ -0,0 +1,41 @@ +#ifndef BLOCKINGQUEUE_H_4LEVMY0N +#define BLOCKINGQUEUE_H_4LEVMY0N + +#include "uscxml/concurrency/tinythread.h" +#include <list> + +namespace uscxml { +namespace concurrency { + +template <class T> +class BlockingQueue { +public: + BlockingQueue() {} + virtual ~BlockingQueue() { + } + + void push(T elem) { + tthread::lock_guard<tthread::mutex> lock(_mutex); + _queue.push_back(elem); + _cond.notify_all(); + } + + T pop() { + tthread::lock_guard<tthread::mutex> lock(_mutex); + while (_queue.empty()) { + _cond.wait(_mutex); + } + T ret = _queue.front(); + _queue.pop_front(); + return ret; + } + + tthread::mutex _mutex; + tthread::condition_variable _cond; + std::list<T> _queue; +}; + +} +} + +#endif /* end of include guard: BLOCKINGQUEUE_H_4LEVMY0N */ diff --git a/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp new file mode 100644 index 0000000..a93b14a --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.cpp @@ -0,0 +1,57 @@ +#include "uscxml/concurrency/DelayedEventQueue.h" +#include <assert.h> + +namespace uscxml { + + DelayedEventQueue::DelayedEventQueue() { + _eventLoop = EV_DEFAULT; + _thread = NULL; + } + + DelayedEventQueue::~DelayedEventQueue() { + ev_break(_eventLoop); + if (_thread) + _thread->join(); + } + + void DelayedEventQueue::addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData) { + if(_timeoutWatcher.find(eventId) != _timeoutWatcher.end()) { + cancelEvent(eventId); + } + + _timeoutWatcher[eventId].eventId = eventId; + _timeoutWatcher[eventId].userData = userData; + _timeoutWatcher[eventId].eventQueue = this; + _timeoutWatcher[eventId].callback = callback; + + ev_timer_init (&_timeoutWatcher[eventId].io, DelayedEventQueue::timerCallback, ((float)delayMs)/1000.0f, 0.); + ev_timer_start (_eventLoop, &_timeoutWatcher[eventId].io); + + } + + void DelayedEventQueue::cancelEvent(std::string eventId) { + if(_timeoutWatcher.find(eventId) != _timeoutWatcher.end()) { + ev_timer_stop(_eventLoop, &_timeoutWatcher[eventId].io); + _timeoutWatcher.erase(eventId); + } + } + + void DelayedEventQueue::start() { + _thread = new tthread::thread(DelayedEventQueue::run, this); + } + + void DelayedEventQueue::stop() { + } + + void DelayedEventQueue::run(void* instance) { + ev_run (((DelayedEventQueue*)instance)->_eventLoop, 0); + } + + void DelayedEventQueue::timerCallback(EV_P_ ev_timer *w, int revents) { + struct callbackData *data = (struct callbackData*)w; + std::string eventId = data->eventId; // copy eventId + data->eventQueue->_timeoutWatcher.erase(data->eventId); + data->callback(data->userData, eventId); + } + +}
\ No newline at end of file diff --git a/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h new file mode 100644 index 0000000..2bc71b2 --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libev/DelayedEventQueue.h @@ -0,0 +1,45 @@ +#ifndef DELAYEDEVENTQUEUE_H_JA6WRBVP +#define DELAYEDEVENTQUEUE_H_JA6WRBVP + +#include "tinythread.h" +#include <ev.h> + +#include <map> +#include <string> +#include <iostream> + +namespace uscxml { + +class DelayedEventQueue { +public: + + struct callbackData + { + ev_timer io; + void *userData; + void (*callback)(void*, const std::string eventId); + std::string eventId; + DelayedEventQueue* eventQueue; + }; + + DelayedEventQueue(); + virtual ~DelayedEventQueue(); + + void addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData); + void cancelEvent(std::string eventId); + + void start(); + void stop(); + static void run(void*); + + static void timerCallback(EV_P_ ev_timer *w, int revents); + + tthread::thread* _thread; + std::map<std::string, callbackData> _timeoutWatcher; + struct ev_loop* _eventLoop; +}; + +} + + +#endif /* end of include guard: DELAYEDEVENTQUEUE_H_JA6WRBVP */ diff --git a/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp new file mode 100644 index 0000000..ce42af7 --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.cpp @@ -0,0 +1,87 @@ +#include "uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h" +#include <assert.h> +#include <event2/event.h> + +namespace uscxml { + + DelayedEventQueue::DelayedEventQueue() { + evthread_use_pthreads(); + _eventLoop = event_base_new(); + _thread = NULL; + } + + DelayedEventQueue::~DelayedEventQueue() { + std::cout << "Deleting DelayedEventQueue" << std::endl; + if(_eventLoop) + event_base_loopbreak(_eventLoop); + if (_thread) + _thread->join(); + if(_eventLoop) + event_base_free(_eventLoop); + } + + void DelayedEventQueue::run(void* instance) { + DelayedEventQueue* THIS = (DelayedEventQueue*)instance; + int result; + while(THIS->_isStarted) { + { + //result = event_base_dispatch(THIS->_eventLoop); + result = event_base_loop(THIS->_eventLoop, EVLOOP_NO_EXIT_ON_EMPTY); + } + } + } + + void DelayedEventQueue::addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData) { + if(_callbackData.find(eventId) != _callbackData.end()) { + cancelEvent(eventId); + } + + struct timeval delay = {delayMs / 1000, (delayMs % 1000) * 1000}; + struct event* event = event_new(_eventLoop, -1, 0, DelayedEventQueue::timerCallback, &_callbackData[eventId]); + + _callbackData[eventId].eventId = eventId; + _callbackData[eventId].userData = userData; + _callbackData[eventId].eventQueue = this; + _callbackData[eventId].callback = callback; + _callbackData[eventId].event = event; + + event_add(event, &delay); + } + + void DelayedEventQueue::cancelEvent(std::string eventId) { + tthread::lock_guard<tthread::recursive_mutex> lock(_mutex); + + if(_callbackData.find(eventId) != _callbackData.end()) { + event_del(_callbackData[eventId].event); + event_free(_callbackData[eventId].event); + _callbackData.erase(eventId); + } + } + + void DelayedEventQueue::start() { + _isStarted = true; + _thread = new tthread::thread(DelayedEventQueue::run, this); + } + + void DelayedEventQueue::stop() { + if (_isStarted) { + _isStarted = false; + _thread->join(); + delete _thread; + } + } + + void DelayedEventQueue::dummyCallback(evutil_socket_t fd, short what, void *arg) { + } + + void DelayedEventQueue::timerCallback(evutil_socket_t fd, short what, void *arg) { + struct callbackData *data = (struct callbackData*)arg; + tthread::lock_guard<tthread::recursive_mutex> lock(data->eventQueue->_mutex); + + std::string eventId = data->eventId; // copy eventId + event_free(data->event); + data->eventQueue->_callbackData.erase(data->eventId); + data->callback(data->userData, eventId); + } + +}
\ No newline at end of file diff --git a/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h new file mode 100644 index 0000000..4c59ce1 --- /dev/null +++ b/src/uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h @@ -0,0 +1,52 @@ +#ifndef DELAYEDEVENTQUEUE_H_JA6WRBVP +#define DELAYEDEVENTQUEUE_H_JA6WRBVP + +#include "uscxml/concurrency/tinythread.h" + +#include <event2/thread.h> +#include <event2/http.h> +#include <event2/event.h> + +#include <map> +#include <string> +#include <iostream> + +namespace uscxml { + +class DelayedEventQueue { +public: + + struct callbackData + { + void *userData; + void (*callback)(void*, const std::string eventId); + std::string eventId; + struct event *event; + DelayedEventQueue* eventQueue; + }; + + DelayedEventQueue(); + virtual ~DelayedEventQueue(); + + void addEvent(std::string eventId, void (*callback)(void*, const std::string eventId), uint32_t delayMs, void* userData); + void cancelEvent(std::string eventId); + + void start(); + void stop(); + static void run(void*); + + static void timerCallback(evutil_socket_t fd, short what, void *arg); + static void dummyCallback(evutil_socket_t fd, short what, void *arg); + + bool _isStarted; + tthread::thread* _thread; + tthread::recursive_mutex _mutex; + + std::map<std::string, callbackData> _callbackData; + struct event_base* _eventLoop; +}; + +} + + +#endif /* end of include guard: DELAYEDEVENTQUEUE_H_JA6WRBVP */ diff --git a/src/uscxml/concurrency/tinythread.cpp b/src/uscxml/concurrency/tinythread.cpp new file mode 100644 index 0000000..690ecee --- /dev/null +++ b/src/uscxml/concurrency/tinythread.cpp @@ -0,0 +1,303 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include <exception> +#include "tinythread.h" + +#if defined(_TTHREAD_POSIX_) + #include <unistd.h> + #include <map> +#elif defined(_TTHREAD_WIN32_) + #include <process.h> +#endif + + +namespace tthread { + +//------------------------------------------------------------------------------ +// condition_variable +//------------------------------------------------------------------------------ +// NOTE 1: The Win32 implementation of the condition_variable class is based on +// the corresponding implementation in GLFW, which in turn is based on a +// description by Douglas C. Schmidt and Irfan Pyarali: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// +// NOTE 2: Windows Vista actually has native support for condition variables +// (InitializeConditionVariable, WakeConditionVariable, etc), but we want to +// be portable with pre-Vista Windows versions, so TinyThread++ does not use +// Vista condition variables. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_WIN32_) + #define _CONDITION_EVENT_ONE 0 + #define _CONDITION_EVENT_ALL 1 +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::condition_variable() : mWaitersCount(0) +{ + mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + InitializeCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +condition_variable::~condition_variable() +{ + CloseHandle(mEvents[_CONDITION_EVENT_ONE]); + CloseHandle(mEvents[_CONDITION_EVENT_ALL]); + DeleteCriticalSection(&mWaitersCountLock); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::_wait() +{ + // Wait for either event to become signaled due to notify_one() or + // notify_all() being called + int result = WaitForMultipleObjects(2, mEvents, FALSE, INFINITE); + + // Check if we are the last waiter + EnterCriticalSection(&mWaitersCountLock); + -- mWaitersCount; + bool lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (mWaitersCount == 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we are the last waiter to be notified to stop waiting, reset the event + if(lastWaiter) + ResetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_one() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ONE]); +} +#endif + +#if defined(_TTHREAD_WIN32_) +void condition_variable::notify_all() +{ + // Are there any waiters? + EnterCriticalSection(&mWaitersCountLock); + bool haveWaiters = (mWaitersCount > 0); + LeaveCriticalSection(&mWaitersCountLock); + + // If we have any waiting threads, send them a signal + if(haveWaiters) + SetEvent(mEvents[_CONDITION_EVENT_ALL]); +} +#endif + + +//------------------------------------------------------------------------------ +// POSIX pthread_t to unique thread::id mapping logic. +// Note: Here we use a global thread safe std::map to convert instances of +// pthread_t to small thread identifier numbers (unique within one process). +// This method should be portable across different POSIX implementations. +//------------------------------------------------------------------------------ + +#if defined(_TTHREAD_POSIX_) +static thread::id _pthread_t_to_ID(const pthread_t &aHandle) +{ + static mutex idMapLock; + static std::map<pthread_t, unsigned long int> idMap; + static unsigned long int idCount(1); + + lock_guard<mutex> guard(idMapLock); + if(idMap.find(aHandle) == idMap.end()) + idMap[aHandle] = idCount ++; + return thread::id(idMap[aHandle]); +} +#endif // _TTHREAD_POSIX_ + + +//------------------------------------------------------------------------------ +// thread +//------------------------------------------------------------------------------ + +/// Information to pass to the new thread (what to run). +struct _thread_start_info { + void (*mFunction)(void *); ///< Pointer to the function to be executed. + void * mArg; ///< Function argument for the thread function. + thread * mThread; ///< Pointer to the thread object. +}; + +// Thread wrapper function. +#if defined(_TTHREAD_WIN32_) +unsigned WINAPI thread::wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) +void * thread::wrapper_function(void * aArg) +#endif +{ + // Get thread startup information + _thread_start_info * ti = (_thread_start_info *) aArg; + + try + { + // Call the actual client thread function + ti->mFunction(ti->mArg); + } + catch(...) + { + // Uncaught exceptions will terminate the application (default behavior + // according to C++11) + std::terminate(); + } + + // The thread is no longer executing + lock_guard<mutex> guard(ti->mThread->mDataMutex); + ti->mThread->mNotAThread = true; + + // The thread is responsible for freeing the startup information + delete ti; + + return 0; +} + +thread::thread(void (*aFunction)(void *), void * aArg) +{ + // Serialize access to this thread structure + lock_guard<mutex> guard(mDataMutex); + + // Fill out the thread startup information (passed to the thread wrapper, + // which will eventually free it) + _thread_start_info * ti = new _thread_start_info; + ti->mFunction = aFunction; + ti->mArg = aArg; + ti->mThread = this; + + // The thread is now alive + mNotAThread = false; + + // Create the thread +#if defined(_TTHREAD_WIN32_) + mHandle = (HANDLE) _beginthreadex(0, 0, wrapper_function, (void *) ti, 0, &mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + if(pthread_create(&mHandle, NULL, wrapper_function, (void *) ti) != 0) + mHandle = 0; +#endif + + // Did we fail to create the thread? + if(!mHandle) + { + mNotAThread = true; + delete ti; + } +} + +thread::~thread() +{ + if(joinable()) + std::terminate(); +} + +void thread::join() +{ + if(joinable()) + { +#if defined(_TTHREAD_WIN32_) + WaitForSingleObject(mHandle, INFINITE); + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_join(mHandle, NULL); +#endif + } +} + +bool thread::joinable() const +{ + mDataMutex.lock(); + bool result = !mNotAThread; + mDataMutex.unlock(); + return result; +} + +void thread::detach() +{ + mDataMutex.lock(); + if(!mNotAThread) + { +#if defined(_TTHREAD_WIN32_) + CloseHandle(mHandle); +#elif defined(_TTHREAD_POSIX_) + pthread_detach(mHandle); +#endif + mNotAThread = true; + } + mDataMutex.unlock(); +} + +thread::id thread::get_id() const +{ + if(!joinable()) + return id(); +#if defined(_TTHREAD_WIN32_) + return id((unsigned long int) mWin32ThreadID); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(mHandle); +#endif +} + +unsigned thread::hardware_concurrency() +{ +#if defined(_TTHREAD_WIN32_) + SYSTEM_INFO si; + GetSystemInfo(&si); + return (int) si.dwNumberOfProcessors; +#elif defined(_SC_NPROCESSORS_ONLN) + return (int) sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROC_ONLN) + return (int) sysconf(_SC_NPROC_ONLN); +#else + // The standard requires this function to return zero if the number of + // hardware cores could not be determined. + return 0; +#endif +} + + +//------------------------------------------------------------------------------ +// this_thread +//------------------------------------------------------------------------------ + +thread::id this_thread::get_id() +{ +#if defined(_TTHREAD_WIN32_) + return thread::id((unsigned long int) GetCurrentThreadId()); +#elif defined(_TTHREAD_POSIX_) + return _pthread_t_to_ID(pthread_self()); +#endif +} + +} diff --git a/src/uscxml/concurrency/tinythread.h b/src/uscxml/concurrency/tinythread.h new file mode 100644 index 0000000..aed7b58 --- /dev/null +++ b/src/uscxml/concurrency/tinythread.h @@ -0,0 +1,714 @@ +/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2010-2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYTHREAD_H_ +#define _TINYTHREAD_H_ + +/// @file +/// @mainpage TinyThread++ API Reference +/// +/// @section intro_sec Introduction +/// TinyThread++ is a minimal, portable implementation of basic threading +/// classes for C++. +/// +/// They closely mimic the functionality and naming of the C++11 standard, and +/// should be easily replaceable with the corresponding std:: variants. +/// +/// @section port_sec Portability +/// The Win32 variant uses the native Win32 API for implementing the thread +/// classes, while for other systems, the POSIX threads API (pthread) is used. +/// +/// @section class_sec Classes +/// In order to mimic the threading API of the C++11 standard, subsets of +/// several classes are provided. The fundamental classes are: +/// @li tthread::thread +/// @li tthread::mutex +/// @li tthread::recursive_mutex +/// @li tthread::condition_variable +/// @li tthread::lock_guard +/// @li tthread::fast_mutex +/// +/// @section misc_sec Miscellaneous +/// The following special keywords are available: #thread_local. +/// +/// For more detailed information (including additional classes), browse the +/// different sections of this documentation. A good place to start is: +/// tinythread.h. + +// Which platform are we on? +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +// Platform specific includes +#if defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include <windows.h> + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#else + #include <pthread.h> + #include <signal.h> + #include <sched.h> + #include <unistd.h> +#endif + +// Generic includes +#include <ostream> + +/// TinyThread++ version (major number). +#define TINYTHREAD_VERSION_MAJOR 1 +/// TinyThread++ version (minor number). +#define TINYTHREAD_VERSION_MINOR 1 +/// TinyThread++ version (full version). +#define TINYTHREAD_VERSION (TINYTHREAD_VERSION_MAJOR * 100 + TINYTHREAD_VERSION_MINOR) + +// Do we have a fully featured C++11 compiler? +#if (__cplusplus > 199711L) || (defined(__STDCXX_VERSION__) && (__STDCXX_VERSION__ >= 201001L)) + #define _TTHREAD_CPP11_ +#endif + +// ...at least partial C++11? +#if defined(_TTHREAD_CPP11_) || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__GXX_EXPERIMENTAL_CPP0X__) + #define _TTHREAD_CPP11_PARTIAL_ +#endif + +// Macro for disabling assignments of objects. +#ifdef _TTHREAD_CPP11_PARTIAL_ + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&) = delete; \ + name& operator=(const name&) = delete; +#else + #define _TTHREAD_DISABLE_ASSIGNMENT(name) \ + name(const name&); \ + name& operator=(const name&); +#endif + +/// @def thread_local +/// Thread local storage keyword. +/// A variable that is declared with the @c thread_local keyword makes the +/// value of the variable local to each thread (known as thread-local storage, +/// or TLS). Example usage: +/// @code +/// // This variable is local to each thread. +/// thread_local int variable; +/// @endcode +/// @note The @c thread_local keyword is a macro that maps to the corresponding +/// compiler directive (e.g. @c __declspec(thread)). While the C++11 standard +/// allows for non-trivial types (e.g. classes with constructors and +/// destructors) to be declared with the @c thread_local keyword, most pre-C++11 +/// compilers only allow for trivial types (e.g. @c int). So, to guarantee +/// portable code, only use trivial types for thread local storage. +/// @note This directive is currently not supported on Mac OS X (it will give +/// a compiler error), since compile-time TLS is not supported in the Mac OS X +/// executable format. Also, some older versions of MinGW (before GCC 4.x) do +/// not support this directive. +/// @hideinitializer + +#if !defined(_TTHREAD_CPP11_) && !defined(thread_local) + #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) + #define thread_local __thread + #else + #define thread_local __declspec(thread) + #endif +#endif + + +/// Main name space for TinyThread++. +/// This namespace is more or less equivalent to the @c std namespace for the +/// C++11 thread classes. For instance, the tthread::mutex class corresponds to +/// the std::mutex class. +namespace tthread { + +/// Mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is non-recursive (i.e. a +/// program may deadlock if the thread that owns a mutex object calls lock() +/// on that object). +/// @see recursive_mutex +class mutex { + public: + /// Constructor. + mutex() +#if defined(_TTHREAD_WIN32_) + : mAlreadyLocked(false) +#endif + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutex_init(&mHandle, NULL); +#endif + } + + /// Destructor. + ~mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); + while(mAlreadyLocked) Sleep(1000); // Simulate deadlock... + mAlreadyLocked = true; +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + bool ret = (TryEnterCriticalSection(&mHandle) ? true : false); + if(ret && mAlreadyLocked) + { + LeaveCriticalSection(&mHandle); + ret = false; + } + return ret; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + mAlreadyLocked = false; + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; + bool mAlreadyLocked; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Recursive mutex class. +/// This is a mutual exclusion object for synchronizing access to shared +/// memory areas for several threads. The mutex is recursive (i.e. a thread +/// may lock the mutex several times, as long as it unlocks the mutex the same +/// number of times). +/// @see mutex +class recursive_mutex { + public: + /// Constructor. + recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + InitializeCriticalSection(&mHandle); +#else + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mHandle, &attr); +#endif + } + + /// Destructor. + ~recursive_mutex() + { +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mHandle); +#else + pthread_mutex_destroy(&mHandle); +#endif + } + + /// Lock the mutex. + /// The method will block the calling thread until a lock on the mutex can + /// be obtained. The mutex remains locked until @c unlock() is called. + /// @see lock_guard + inline void lock() + { +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mHandle); +#else + pthread_mutex_lock(&mHandle); +#endif + } + + /// Try to lock the mutex. + /// The method will try to lock the mutex. If it fails, the function will + /// return immediately (non-blocking). + /// @return @c true if the lock was acquired, or @c false if the lock could + /// not be acquired. + inline bool try_lock() + { +#if defined(_TTHREAD_WIN32_) + return TryEnterCriticalSection(&mHandle) ? true : false; +#else + return (pthread_mutex_trylock(&mHandle) == 0) ? true : false; +#endif + } + + /// Unlock the mutex. + /// If any threads are waiting for the lock on this mutex, one of them will + /// be unblocked. + inline void unlock() + { +#if defined(_TTHREAD_WIN32_) + LeaveCriticalSection(&mHandle); +#else + pthread_mutex_unlock(&mHandle); +#endif + } + + _TTHREAD_DISABLE_ASSIGNMENT(recursive_mutex) + + private: +#if defined(_TTHREAD_WIN32_) + CRITICAL_SECTION mHandle; +#else + pthread_mutex_t mHandle; +#endif + + friend class condition_variable; +}; + +/// Lock guard class. +/// The constructor locks the mutex, and the destructor unlocks the mutex, so +/// the mutex will automatically be unlocked when the lock guard goes out of +/// scope. Example usage: +/// @code +/// mutex m; +/// int counter; +/// +/// void increment() +/// { +/// lock_guard<mutex> guard(m); +/// ++ counter; +/// } +/// @endcode + +template <class T> +class lock_guard { + public: + typedef T mutex_type; + + lock_guard() : mMutex(0) {} + + /// The constructor locks the mutex. + explicit lock_guard(mutex_type &aMutex) + { + mMutex = &aMutex; + mMutex->lock(); + } + + /// The destructor unlocks the mutex. + ~lock_guard() + { + if(mMutex) + mMutex->unlock(); + } + + private: + mutex_type * mMutex; +}; + +/// Condition variable class. +/// This is a signalling object for synchronizing the execution flow for +/// several threads. Example usage: +/// @code +/// // Shared data and associated mutex and condition variable objects +/// int count; +/// mutex m; +/// condition_variable cond; +/// +/// // Wait for the counter to reach a certain number +/// void wait_counter(int targetCount) +/// { +/// lock_guard<mutex> guard(m); +/// while(count < targetCount) +/// cond.wait(m); +/// } +/// +/// // Increment the counter, and notify waiting threads +/// void increment() +/// { +/// lock_guard<mutex> guard(m); +/// ++ count; +/// cond.notify_all(); +/// } +/// @endcode +class condition_variable { + public: + /// Constructor. +#if defined(_TTHREAD_WIN32_) + condition_variable(); +#else + condition_variable() + { + pthread_cond_init(&mHandle, NULL); + } +#endif + + /// Destructor. +#if defined(_TTHREAD_WIN32_) + ~condition_variable(); +#else + ~condition_variable() + { + pthread_cond_destroy(&mHandle); + } +#endif + + /// Wait for the condition. + /// The function will block the calling thread until the condition variable + /// is woken by @c notify_one(), @c notify_all() or a spurious wake up. + /// @param[in] aMutex A mutex that will be unlocked when the wait operation + /// starts, an locked again as soon as the wait operation is finished. + template <class _mutexT> + inline void wait(_mutexT &aMutex) + { +#if defined(_TTHREAD_WIN32_) + // Increment number of waiters + EnterCriticalSection(&mWaitersCountLock); + ++ mWaitersCount; + LeaveCriticalSection(&mWaitersCountLock); + + // Release the mutex while waiting for the condition (will decrease + // the number of waiters when done)... + aMutex.unlock(); + _wait(); + aMutex.lock(); +#else + pthread_cond_wait(&mHandle, &aMutex.mHandle); +#endif + } + + /// Notify one thread that is waiting for the condition. + /// If at least one thread is blocked waiting for this condition variable, + /// one will be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_one(); +#else + inline void notify_one() + { + pthread_cond_signal(&mHandle); + } +#endif + + /// Notify all threads that are waiting for the condition. + /// All threads that are blocked waiting for this condition variable will + /// be woken up. + /// @note Only threads that started waiting prior to this call will be + /// woken up. +#if defined(_TTHREAD_WIN32_) + void notify_all(); +#else + inline void notify_all() + { + pthread_cond_broadcast(&mHandle); + } +#endif + + _TTHREAD_DISABLE_ASSIGNMENT(condition_variable) + + private: +#if defined(_TTHREAD_WIN32_) + void _wait(); + HANDLE mEvents[2]; ///< Signal and broadcast event HANDLEs. + unsigned int mWaitersCount; ///< Count of the number of waiters. + CRITICAL_SECTION mWaitersCountLock; ///< Serialize access to mWaitersCount. +#else + pthread_cond_t mHandle; +#endif +}; + + +/// Thread class. +class thread { + public: +#if defined(_TTHREAD_WIN32_) + typedef HANDLE native_handle_type; +#else + typedef pthread_t native_handle_type; +#endif + + class id; + + /// Default constructor. + /// Construct a @c thread object without an associated thread of execution + /// (i.e. non-joinable). + thread() : mHandle(0), mNotAThread(true) +#if defined(_TTHREAD_WIN32_) + , mWin32ThreadID(0) +#endif + {} + + /// Thread starting constructor. + /// Construct a @c thread object with a new thread of execution. + /// @param[in] aFunction A function pointer to a function of type: + /// <tt>void fun(void * arg)</tt> + /// @param[in] aArg Argument to the thread function. + /// @note This constructor is not fully compatible with the standard C++ + /// thread class. It is more similar to the pthread_create() (POSIX) and + /// CreateThread() (Windows) functions. + thread(void (*aFunction)(void *), void * aArg); + + /// Destructor. + /// @note If the thread is joinable upon destruction, @c std::terminate() + /// will be called, which terminates the process. It is always wise to do + /// @c join() before deleting a thread object. + ~thread(); + + /// Wait for the thread to finish (join execution flows). + /// After calling @c join(), the thread object is no longer associated with + /// a thread of execution (i.e. it is not joinable, and you may not join + /// with it nor detach from it). + void join(); + + /// Check if the thread is joinable. + /// A thread object is joinable if it has an associated thread of execution. + bool joinable() const; + + /// Detach from the thread. + /// After calling @c detach(), the thread object is no longer assicated with + /// a thread of execution (i.e. it is not joinable). The thread continues + /// execution without the calling thread blocking, and when the thread + /// ends execution, any owned resources are released. + void detach(); + + /// Return the thread ID of a thread object. + id get_id() const; + + /// Get the native handle for this thread. + /// @note Under Windows, this is a @c HANDLE, and under POSIX systems, this + /// is a @c pthread_t. + inline native_handle_type native_handle() + { + return mHandle; + } + + /// Determine the number of threads which can possibly execute concurrently. + /// This function is useful for determining the optimal number of threads to + /// use for a task. + /// @return The number of hardware thread contexts in the system. + /// @note If this value is not defined, the function returns zero (0). + static unsigned hardware_concurrency(); + + _TTHREAD_DISABLE_ASSIGNMENT(thread) + + private: + native_handle_type mHandle; ///< Thread handle. + mutable mutex mDataMutex; ///< Serializer for access to the thread private data. + bool mNotAThread; ///< True if this object is not a thread of execution. +#if defined(_TTHREAD_WIN32_) + unsigned int mWin32ThreadID; ///< Unique thread ID (filled out by _beginthreadex). +#endif + + // This is the internal thread wrapper function. +#if defined(_TTHREAD_WIN32_) + static unsigned WINAPI wrapper_function(void * aArg); +#else + static void * wrapper_function(void * aArg); +#endif +}; + +/// Thread ID. +/// The thread ID is a unique identifier for each thread. +/// @see thread::get_id() +class thread::id { + public: + /// Default constructor. + /// The default constructed ID is that of thread without a thread of + /// execution. + id() : mId(0) {}; + + id(unsigned long int aId) : mId(aId) {}; + + id(const id& aId) : mId(aId.mId) {}; + + inline id & operator=(const id &aId) + { + mId = aId.mId; + return *this; + } + + inline friend bool operator==(const id &aId1, const id &aId2) + { + return (aId1.mId == aId2.mId); + } + + inline friend bool operator!=(const id &aId1, const id &aId2) + { + return (aId1.mId != aId2.mId); + } + + inline friend bool operator<=(const id &aId1, const id &aId2) + { + return (aId1.mId <= aId2.mId); + } + + inline friend bool operator<(const id &aId1, const id &aId2) + { + return (aId1.mId < aId2.mId); + } + + inline friend bool operator>=(const id &aId1, const id &aId2) + { + return (aId1.mId >= aId2.mId); + } + + inline friend bool operator>(const id &aId1, const id &aId2) + { + return (aId1.mId > aId2.mId); + } + + inline friend std::ostream& operator <<(std::ostream &os, const id &obj) + { + os << obj.mId; + return os; + } + + private: + unsigned long int mId; +}; + + +// Related to <ratio> - minimal to be able to support chrono. +typedef long long __intmax_t; + +/// Minimal implementation of the @c ratio class. This class provides enough +/// functionality to implement some basic @c chrono classes. +template <__intmax_t N, __intmax_t D = 1> class ratio { + public: + static double _as_double() { return double(N) / double(D); } +}; + +/// Minimal implementation of the @c chrono namespace. +/// The @c chrono namespace provides types for specifying time intervals. +namespace chrono { + /// Duration template class. This class provides enough functionality to + /// implement @c this_thread::sleep_for(). + template <class _Rep, class _Period = ratio<1> > class duration { + private: + _Rep rep_; + public: + typedef _Rep rep; + typedef _Period period; + + /// Construct a duration object with the given duration. + template <class _Rep2> + explicit duration(const _Rep2& r) : rep_(r) {}; + + /// Return the value of the duration object. + rep count() const + { + return rep_; + } + }; + + // Standard duration types. + typedef duration<__intmax_t, ratio<1, 1000000000> > nanoseconds; ///< Duration with the unit nanoseconds. + typedef duration<__intmax_t, ratio<1, 1000000> > microseconds; ///< Duration with the unit microseconds. + typedef duration<__intmax_t, ratio<1, 1000> > milliseconds; ///< Duration with the unit milliseconds. + typedef duration<__intmax_t> seconds; ///< Duration with the unit seconds. + typedef duration<__intmax_t, ratio<60> > minutes; ///< Duration with the unit minutes. + typedef duration<__intmax_t, ratio<3600> > hours; ///< Duration with the unit hours. +} + +/// The namespace @c this_thread provides methods for dealing with the +/// calling thread. +namespace this_thread { + /// Return the thread ID of the calling thread. + thread::id get_id(); + + /// Yield execution to another thread. + /// Offers the operating system the opportunity to schedule another thread + /// that is ready to run on the current processor. + inline void yield() + { +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif + } + + /// Blocks the calling thread for a period of time. + /// @param[in] aTime Minimum time to put the thread to sleep. + /// Example usage: + /// @code + /// // Sleep for 100 milliseconds + /// this_thread::sleep_for(chrono::milliseconds(100)); + /// @endcode + /// @note Supported duration types are: nanoseconds, microseconds, + /// milliseconds, seconds, minutes and hours. + template <class _Rep, class _Period> void sleep_for(const chrono::duration<_Rep, _Period>& aTime) + { +#if defined(_TTHREAD_WIN32_) + Sleep(int(double(aTime.count()) * (1000.0 * _Period::_as_double()) + 0.5)); +#else + usleep(int(double(aTime.count()) * (1000000.0 * _Period::_as_double()) + 0.5)); +#endif + } +} + +} + +// Define/macro cleanup +#undef _TTHREAD_DISABLE_ASSIGNMENT + +#endif // _TINYTHREAD_H_ diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp new file mode 100644 index 0000000..e31438a --- /dev/null +++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.cpp @@ -0,0 +1,289 @@ +#include "uscxml/datamodel/ecmascript/v8/V8DataModel.h" +#include "uscxml/Message.h" + +namespace uscxml { + +V8DataModel::V8DataModel() { +// _contexts.push_back(v8::Context::New()); +} + +DataModel* V8DataModel::create(Interpreter* interpreter) { + V8DataModel* dm = new V8DataModel(); + dm->_interpreter = interpreter; + v8::Locker locker; + v8::HandleScope scope; + + // see http://stackoverflow.com/questions/3171418/v8-functiontemplate-class-instance + dm->_globalTemplate = v8::Persistent<v8::ObjectTemplate>(v8::ObjectTemplate::New()); + dm->_globalTemplate->Set(v8::String::New("In"), v8::FunctionTemplate::New(jsIn, v8::External::New(reinterpret_cast<void*>(this)))); + + dm->_contexts.push_back(v8::Context::New(0, _globalTemplate)); + dm->setName(interpreter->getName()); + dm->setSessionId(interpreter->getSessionId()); + dm->eval("_ioprocessors = {};"); + return dm; +} + +void V8DataModel::setSessionId(const std::string& sessionId) { + _sessionId = sessionId; + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle<v8::Object> global = _contexts.front()->Global(); + + global->Set(v8::String::New("_sessionid"), v8::String::New(sessionId.c_str())); +} + +void V8DataModel::setName(const std::string& name) { + _name = name; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle<v8::Object> global = _contexts.front()->Global(); + + global->Set(v8::String::New("_name"), v8::String::New(name.c_str())); +} + +V8DataModel::~V8DataModel() { + while(_contexts.size() > 0) { + _contexts.back().Dispose(); + _contexts.pop_back(); + } +} + +void V8DataModel::pushContext() { + _contexts.push_back(_contexts.back().New(_contexts.back())); +} + +void V8DataModel::popContext() { + if (_contexts.size() > 1) { + _contexts.back().Dispose(); + _contexts.pop_back(); + } +} + +void V8DataModel::initialize() { +} + +void V8DataModel::setEvent(Event& event) { + _event = event; + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle<v8::Object> global = _contexts.front()->Global(); + + // this is unfortunate - can't we store the template in the object? + if (_eventTemplate.IsEmpty()) { + v8::Handle<v8::ObjectTemplate> localEventTemplate = v8::ObjectTemplate::New(); + localEventTemplate->SetInternalFieldCount(1); // we only have a single C++ object + localEventTemplate->SetAccessor(v8::String::New("name"), V8DataModel::jsGetEventName); + localEventTemplate->SetAccessor(v8::String::New("type"), V8DataModel::jsGetEventType); + localEventTemplate->SetAccessor(v8::String::New("sendid"), V8DataModel::jsGetEventSendId); + localEventTemplate->SetAccessor(v8::String::New("origin"), V8DataModel::jsGetEventOrigin); + localEventTemplate->SetAccessor(v8::String::New("origintype"), V8DataModel::jsGetEventOriginType); + localEventTemplate->SetAccessor(v8::String::New("invokeid"), V8DataModel::jsGetEventInvokeId); + _eventTemplate = v8::Persistent<v8::ObjectTemplate>::New(localEventTemplate); + } + + assert(_eventTemplate->InternalFieldCount() == 1); + v8::Handle<v8::Object> eventJS = _eventTemplate->NewInstance(); + eventJS->SetInternalField(0, v8::External::New(&event)); + + eventJS->Set(v8::String::New("data"), getDataAsValue(event)); // set data part of _event + global->Set(v8::String::New("_event"), eventJS); +} + +void V8DataModel::setData(const std::string& key, Data& data) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.front()); + v8::Handle<v8::Object> global = _contexts.front()->Global(); + global->Set(v8::String::New(key.c_str()), getDataAsValue(data)); +} + +v8::Handle<v8::Value> V8DataModel::getDataAsValue(Data& data) { + if (data.compound.size() > 0) { + v8::Handle<v8::Object> value = v8::Array::New(); + std::map<std::string, Data>::iterator compoundIter = data.compound.begin(); + while(compoundIter != data.compound.end()) { + value->Set(v8::String::New(compoundIter->first.c_str()), getDataAsValue(compoundIter->second)); + compoundIter++; + } + return value; + } + if (data.array.size() > 0) { + v8::Handle<v8::Object> value = v8::Array::New(); + std::vector<Data>::iterator arrayIter = data.array.begin(); + uint32_t index = 0; + while(arrayIter != data.array.end()) { + value->Set(index++, getDataAsValue(*arrayIter)); + arrayIter++; + } + return value; + } + if (data.type == Data::VERBATIM) { + return v8::String::New(data.atom.c_str()); + } else { + return evalAsValue(data.atom); + } +} + +v8::Handle<v8::Value> V8DataModel::jsIn(const v8::Arguments& args) { + V8DataModel* THIS = static_cast<V8DataModel*>(v8::External::Unwrap(args.Data())); + for (unsigned int i = 0; i < args.Length(); i++) { + if (args[i]->IsString()) { + std::string stateName(*v8::String::AsciiValue(args[i]->ToString())); + if (Interpreter::isMember(THIS->_interpreter->getState(stateName), THIS->_interpreter->getConfiguration())) { + continue; + } + } + return v8::Boolean::New(false); + } + return v8::Boolean::New(true); +} + +v8::Handle<v8::Value> V8DataModel::jsGetEventName(v8::Local<v8::String> property, + const v8::AccessorInfo &info) { + Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->name.c_str()); +} + +v8::Handle<v8::Value> V8DataModel::jsGetEventType(v8::Local<v8::String> property, + const v8::AccessorInfo &info) { + Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value()); + switch (event->type) { + case Event::PLATFORM: + return v8::String::New("platform"); + break; + case Event::INTERNAL: + return v8::String::New("internal"); + break; + case Event::EXTERNAL: + return v8::String::New("external"); + break; + default: + return v8::String::New(""); + break; + } +} + +v8::Handle<v8::Value> V8DataModel::jsGetEventSendId(v8::Local<v8::String> property, + const v8::AccessorInfo &info) { + Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->sendid.c_str()); + +} + +v8::Handle<v8::Value> V8DataModel::jsGetEventOrigin(v8::Local<v8::String> property, + const v8::AccessorInfo &info) { + Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->origin.c_str()); +} + +v8::Handle<v8::Value> V8DataModel::jsGetEventOriginType(v8::Local<v8::String> property, + const v8::AccessorInfo &info) { + Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->origintype.c_str()); +} + +v8::Handle<v8::Value> V8DataModel::jsGetEventInvokeId(v8::Local<v8::String> property, + const v8::AccessorInfo &info) { + Event* event = static_cast<Event*>(v8::Local<v8::External>::Cast(info.Holder()->GetInternalField(0))->Value()); + return v8::String::New(event->invokeid.c_str()); +} + +bool V8DataModel::validate(const std::string& location, const std::string& schema) { + return true; +} + +uint32_t V8DataModel::getLength(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + v8::Handle<v8::Array> result = evalAsValue(expr).As<v8::Array>(); + return result->Length(); +} + +void V8DataModel::eval(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + evalAsValue(expr); +} + +bool V8DataModel::evalAsBool(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + v8::Handle<v8::Value> result = evalAsValue(expr); + return(result->ToBoolean()->BooleanValue()); +} + +std::string V8DataModel::evalAsString(const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + v8::Handle<v8::Value> result = evalAsValue(expr); + v8::String::AsciiValue data(result->ToString()); + return std::string(*data); +} + +void V8DataModel::assign(const std::string& location, const std::string& expr) { + v8::Locker locker; + v8::HandleScope handleScope; + v8::Context::Scope contextScope(_contexts.back()); + evalAsValue((location + " = " + expr).c_str()); +} + +v8::Handle<v8::Value> V8DataModel::evalAsValue(const std::string& expr) { + v8::TryCatch tryCatch; + v8::Handle<v8::String> source = v8::String::New(expr.c_str()); + v8::Handle<v8::Script> script = v8::Script::Compile(source); + + v8::Handle<v8::Value> result; + if (!script.IsEmpty()) + result = script->Run(); + + if (script.IsEmpty() || result.IsEmpty()) { + // throw an exception + assert(tryCatch.HasCaught()); + Event exceptionEvent; + exceptionEvent.name = "error.execution"; + + std::string exceptionString(*v8::String::AsciiValue(tryCatch.Exception())); + exceptionEvent.compound["exception"] = Data(exceptionString, Data::VERBATIM);; + + v8::Handle<v8::Message> message = tryCatch.Message(); + if (!message.IsEmpty()) { + std::string filename(*v8::String::AsciiValue(message->GetScriptResourceName())); + exceptionEvent.compound["filename"] = Data(filename, Data::VERBATIM); + + std::string sourceLine(*v8::String::AsciiValue(message->GetSourceLine())); + exceptionEvent.compound["sourceline"] = Data(sourceLine, Data::VERBATIM); + + std::stringstream ssLineNumber; + int lineNumber = message->GetLineNumber(); + ssLineNumber << lineNumber; + exceptionEvent.compound["linenumber"] = Data(ssLineNumber.str()); + + int startColumn = message->GetStartColumn(); + int endColumn = message->GetEndColumn(); + std::stringstream ssUnderline; + for (int i = 0; i < startColumn; i++) + ssUnderline << " "; + for (int i = startColumn; i < endColumn; i++) + ssUnderline << "^"; + exceptionEvent.compound["sourcemark"] = Data(ssUnderline.str(), Data::VERBATIM); + + std::string stackTrace(*v8::String::AsciiValue(tryCatch.StackTrace())); + exceptionEvent.compound["stacktrace"] = Data(stackTrace, Data::VERBATIM); + + } + + _interpreter->receiveInternal(exceptionEvent); + throw(exceptionEvent); + } + + return result; +} + +}
\ No newline at end of file diff --git a/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h new file mode 100644 index 0000000..7522679 --- /dev/null +++ b/src/uscxml/datamodel/ecmascript/v8/V8DataModel.h @@ -0,0 +1,73 @@ +#ifndef V8DATAMODEL_H_KN8TWG0V +#define V8DATAMODEL_H_KN8TWG0V + +#include "uscxml/Interpreter.h" +#include <list> +#include <v8.h> + +namespace uscxml { + class Event; + class Data; +} + +namespace uscxml { + +class V8DataModel : public DataModel { +public: + V8DataModel(); + virtual ~V8DataModel(); + virtual DataModel* create(Interpreter* interpreter); + + virtual void initialize(); + virtual void setSessionId(const std::string& sessionId); + virtual void setName(const std::string& name); + virtual void setEvent(Event& event); + virtual void setData(const std::string& key, Data& event); + virtual v8::Handle<v8::Value> getDataAsValue(Data& data); + + virtual bool validate(const std::string& location, const std::string& schema); + + virtual uint32_t getLength(const std::string& expr); + virtual void pushContext(); + virtual void popContext(); + + virtual void eval(const std::string& expr); + virtual void assign(const std::string& location, const std::string& expr); + + virtual std::string evalAsString(const std::string& expr); + virtual bool evalAsBool(const std::string& expr); + + static v8::Handle<v8::Value> jsGetEventName(v8::Local<v8::String> property, + const v8::AccessorInfo &info); + static v8::Handle<v8::Value> jsGetEventType(v8::Local<v8::String> property, + const v8::AccessorInfo &info); + static v8::Handle<v8::Value> jsGetEventSendId(v8::Local<v8::String> property, + const v8::AccessorInfo &info); + static v8::Handle<v8::Value> jsGetEventOrigin(v8::Local<v8::String> property, + const v8::AccessorInfo &info); + static v8::Handle<v8::Value> jsGetEventOriginType(v8::Local<v8::String> property, + const v8::AccessorInfo &info); + static v8::Handle<v8::Value> jsGetEventInvokeId(v8::Local<v8::String> property, + const v8::AccessorInfo &info); + + static v8::Handle<v8::Value> jsIn(const v8::Arguments& args); + + +protected: + std::list<v8::Persistent<v8::Context> > _contexts; + Interpreter* _interpreter; + + std::string _sessionId; + std::string _name; + + Event _event; + v8::Persistent<v8::ObjectTemplate> _globalTemplate; + v8::Persistent<v8::ObjectTemplate> _eventTemplate; + + v8::Handle<v8::Value> evalAsValue(const std::string& expr); + +}; + +} + +#endif /* end of include guard: V8DATAMODEL_H_KN8TWG0V */ diff --git a/src/uscxml/ioprocessor/basichttp/README.md b/src/uscxml/ioprocessor/basichttp/README.md new file mode 100644 index 0000000..de89944 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/README.md @@ -0,0 +1,2 @@ +Only the libevent basichttp ioprocessor is supported. Mongoose seemed somewhat +unmaintained and pion comes with too many dependencies.
\ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp new file mode 100644 index 0000000..c06c7e8 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.cpp @@ -0,0 +1,361 @@ +#include "uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h" +#include "uscxml/Message.h" +#include <iostream> +#include <event2/dns.h> +#include <event2/buffer.h> +#include <event2/keyvalq_struct.h> + +#include <string.h> + +#include <io/uri.hpp> +#include <glog/logging.h> + +#include <netdb.h> +#include <arpa/inet.h> + +namespace uscxml { +namespace io { +namespace libevent { + +// see http://www.w3.org/TR/scxml/#BasicHTTPEventProcessor + +EventIOProcessor::EventIOProcessor() { +} + +EventIOProcessor::~EventIOProcessor() { + _eventQueue.stop(); + EventIOServer* httpServer = EventIOServer::getInstance(); + httpServer->unregisterProcessor(this); +} + +IOProcessor* EventIOProcessor::create(Interpreter* interpreter) { + EventIOProcessor* io = new EventIOProcessor(); + io->_interpreter = interpreter; + + io->_dns = evdns_base_new(io->_eventQueue._eventLoop, 1); + assert(io->_dns); + assert(evdns_base_count_nameservers(io->_dns) > 0); + + // register at http server + EventIOServer* httpServer = EventIOServer::getInstance(); + httpServer->registerProcessor(io); + + io->start(); + return io; +} + +void EventIOProcessor::start() { + _eventQueue.start(); +} + +void EventIOProcessor::send(SendRequest& req) { + // I cant figure out how to copy the reference into the struct :( + _sendData[req.sendid].req = req; + _sendData[req.sendid].ioProcessor = this; + + if (req.delayMs > 0) { + LOG(INFO) << "Enqueing HTTP send request"; + _eventQueue.addEvent(req.sendid, EventIOProcessor::httpMakeSendReq, req.delayMs, &_sendData[req.sendid]); + } else { + LOG(INFO) << "Sending HTTP send request"; + EventIOProcessor::httpMakeSendReq(&_sendData[req.sendid], req.sendid); + } +} + +void EventIOProcessor::httpMakeSendReq(void* userdata, std::string eventName) { + SendData* sendData = ((SendData*)userdata); + EventIOProcessor* THIS = sendData->ioProcessor; + int err = 0; + char uriBuf[1024]; + + struct evhttp_uri* targetURI = evhttp_uri_parse(sendData->req.target.c_str()); + if (evhttp_uri_get_port(targetURI) == 0) + evhttp_uri_set_port(targetURI, 80); + const char* hostName = evhttp_uri_get_host(targetURI); + + // use synchronous dns resolving for multicast dns + if(strlen(hostName) >= strlen(".local")) { + if(strcmp(hostName + strlen(hostName) - strlen(".local"), ".local") == 0) { + evhttp_uri_set_host(targetURI, EventIOServer::syncResolve(hostName).c_str()); + } + } + evhttp_uri_join(targetURI, uriBuf, 1024); + + LOG(INFO) << "URI for send request: " << uriBuf << std::endl; + + std::stringstream ssEndPoint; + ssEndPoint << evhttp_uri_get_host(targetURI) << ":" << evhttp_uri_get_port(targetURI); + std::string endPoint = ssEndPoint.str(); + + std::stringstream ssLocalURI; + ssLocalURI << evhttp_uri_get_path(targetURI) << evhttp_uri_get_fragment(targetURI); + std::string localURI = ssLocalURI.str(); + + if (THIS->_httpConnections.find(endPoint) == THIS->_httpConnections.end()) + THIS->_httpConnections[endPoint] = evhttp_connection_base_new(THIS->_eventQueue._eventLoop, THIS->_dns, evhttp_uri_get_host(targetURI), evhttp_uri_get_port(targetURI)); + + struct evhttp_connection* httpConn = THIS->_httpConnections[endPoint]; + struct evhttp_request* httpReq = evhttp_request_new(EventIOProcessor::httpSendReqDone, userdata); + +#if 0 + // event name + if (sendData->req.event.size() > 0) { + evhttp_add_header(evhttp_request_get_output_headers(httpReq), "_scxmleventname", evhttp_encode_uri(sendData->req.event.c_str())); + } + + // event namelist + if (sendData->req.namelist.size() > 0) { + std::map<std::string, std::string>::iterator namelistIter = sendData->req.namelist.begin(); + while (namelistIter != sendData->req.namelist.end()) { + evhttp_add_header(evhttp_request_get_output_headers(httpReq), + namelistIter->first.c_str(), + evhttp_encode_uri(namelistIter->second.c_str())); + namelistIter++; + } + } + + // event params + if (sendData->req.params.size() > 0) { + std::map<std::string, std::string>::iterator paramIter = sendData->req.params.begin(); + while (paramIter != sendData->req.params.end()) { + evhttp_add_header(evhttp_request_get_output_headers(httpReq), + paramIter->first.c_str(), + evhttp_encode_uri(paramIter->second.c_str())); + paramIter++; + } + } + + // content + if (sendData->req.content.size() > 0) + evbuffer_add(evhttp_request_get_output_buffer(httpReq), sendData->req.content.c_str(), sendData->req.content.size()); +#endif + + evhttp_add_header(evhttp_request_get_output_headers(httpReq), "_scxmleventstruct", evhttp_encode_uri(sendData->req.toXMLString().c_str())); + + + THIS->_httpRequests[sendData->req.sendid] = httpReq; + err = evhttp_make_request(httpConn, + httpReq, + EVHTTP_REQ_POST, localURI.c_str()); + if (err) { + LOG(ERROR) << "Could not make http request to " << sendData->req.target; + } +} + +void EventIOProcessor::httpRecvReq(struct evhttp_request *req, void *arg) { + + const char *cmdtype; + struct evkeyvalq *headers; + struct evkeyval *header; + struct evbuffer *buf; + + switch (evhttp_request_get_command(req)) { + case EVHTTP_REQ_GET: cmdtype = "GET"; break; + case EVHTTP_REQ_POST: cmdtype = "POST"; break; + case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break; + case EVHTTP_REQ_PUT: cmdtype = "PUT"; break; + case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break; + case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break; + case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break; + case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break; + case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break; + default: cmdtype = "unknown"; break; + } + + Event reqEvent; + reqEvent.type = Event::EXTERNAL; + + // map headers to event structure + headers = evhttp_request_get_input_headers(req); + for (header = headers->tqh_first; header; + header = header->next.tqe_next) { +// std::cout << "Header: " << header->key << std::endl; +// std::cout << "Value: " << evhttp_decode_uri(header->value) << std::endl; + if (boost::iequals("_scxmleventstruct", header->key)) { + reqEvent = Event::fromXML(evhttp_decode_uri(header->value)); + break; + } else if (boost::iequals("_scxmleventname", header->key)) { + reqEvent.name = evhttp_decode_uri(header->value); + } else { + reqEvent.compound[header->key] = Data(evhttp_decode_uri(header->value), Data::VERBATIM); + } + } + + // get content into event + std::string content; + buf = evhttp_request_get_input_buffer(req); + while (evbuffer_get_length(buf)) { + int n; + char cbuf[128]; + n = evbuffer_remove(buf, cbuf, sizeof(buf)-1); + if (n > 0) { + content.append(cbuf, n); + } + } + reqEvent.compound["content"] = Data(content, Data::VERBATIM); + + EventIOProcessor* THIS = (EventIOProcessor*)arg; + THIS->_interpreter->receive(reqEvent); + + evhttp_send_reply(req, 200, "OK", NULL); +} + +void EventIOProcessor::httpSendReqDone(struct evhttp_request *req, void *cb_arg) { + if (req) { + LOG(INFO) << "got return code " << evhttp_request_get_response_code(req) << std::endl; + } +} + +void EventIOProcessor::invoke(InvokeRequest& req) { + +} +void EventIOProcessor::cancel(const std::string sendId) { + +} + +EventIOServer::EventIOServer(unsigned short port) { + _port = port; + _base = event_base_new(); + _http = evhttp_new(_base); + _handle = NULL; + while((_handle = evhttp_bind_socket_with_handle(_http, INADDR_ANY, _port)) == NULL) { + _port++; + } + determineAddress(); +} + +EventIOServer::~EventIOServer() { +} + +EventIOServer* EventIOServer::_instance = NULL; +tthread::recursive_mutex EventIOServer::_instanceMutex; + +EventIOServer* EventIOServer::getInstance() { + tthread::lock_guard<tthread::recursive_mutex> lock(_instanceMutex); + if (_instance == NULL) { + _instance = new EventIOServer(8080); + _instance->start(); + } + return _instance; +} + +void EventIOServer::registerProcessor(EventIOProcessor* processor) { + EventIOServer* THIS = getInstance(); + tthread::lock_guard<tthread::recursive_mutex> lock(THIS->_mutex); + + /** + * Determine path for interpreter. + * + * If the interpreter has a name and it is not yet taken, choose it as the path + * for requests. If the interpreters name path is already taken, append digits + * until we have an available path. + * + * If the interpreter does not specify a name, take its sessionid. + */ + + std::string path = processor->_interpreter->getName(); + if (path.size() == 0) { + path = processor->_interpreter->getSessionId(); + } + assert(path.size() > 0); + + std::stringstream actualPath(path); + int i = 1; + while(THIS->_processors.find(actualPath.str()) != THIS->_processors.end()) { + actualPath.str(std::string()); + actualPath.clear(); + actualPath << path << ++i; + } + + std::stringstream processorURL; + processorURL << "http://" << THIS->_address << ":" << THIS->_port << "/" << actualPath.str(); + + THIS->_processors[actualPath.str()] = processor; + processor->setURL(processorURL.str()); + + evhttp_set_cb(THIS->_http, ("/" + actualPath.str()).c_str(), EventIOProcessor::httpRecvReq, processor); +// evhttp_set_cb(THIS->_http, "/", EventIOProcessor::httpRecvReq, processor); +// evhttp_set_gencb(THIS->_http, EventIOProcessor::httpRecvReq, NULL); +} + +void EventIOServer::unregisterProcessor(EventIOProcessor* processor) { + EventIOServer* THIS = getInstance(); + tthread::lock_guard<tthread::recursive_mutex> lock(THIS->_mutex); + evhttp_del_cb(THIS->_http, processor->getURL().c_str()); +} + +void EventIOServer::start() { + _isRunning = true; + _thread = new tthread::thread(EventIOServer::run, this); +} + +void EventIOServer::run(void* instance) { + EventIOServer* THIS = (EventIOServer*)instance; + while(THIS->_isRunning) { + LOG(INFO) << "Dispatching HTTP Server" << std::endl; + event_base_dispatch(THIS->_base); + } + LOG(INFO) << "HTTP Server stopped" << std::endl; +} + +std::string EventIOServer::syncResolve(const std::string& hostname) { + struct hostent *he; + struct in_addr **addr_list; + int i; + + if ( (he = gethostbyname( hostname.c_str() ) ) != NULL) { + addr_list = (struct in_addr **) he->h_addr_list; + for(i = 0; addr_list[i] != NULL; i++) { + return std::string(inet_ntoa(*addr_list[i])); + } + } + return ""; +} + +void EventIOServer::determineAddress() { + + char hostname[1024]; + gethostname(hostname, 1024); + _address = std::string(hostname); + +#if 0 + struct sockaddr_storage ss; + evutil_socket_t fd; + ev_socklen_t socklen = sizeof(ss); + char addrbuf[128]; + + void *inaddr; + const char *addr; + int got_port = -1; + fd = evhttp_bound_socket_get_fd(_handle); + memset(&ss, 0, sizeof(ss)); + if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) { + perror("getsockname() failed"); + return; + } + + if (ss.ss_family == AF_INET) { + got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port); + inaddr = &((struct sockaddr_in*)&ss)->sin_addr; + } else if (ss.ss_family == AF_INET6) { + got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port); + inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr; + } else { + fprintf(stderr, "Weird address family %d\n", + ss.ss_family); + return; + } + addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, + sizeof(addrbuf)); + if (addr) { + _address = std::string(addr); + } else { + fprintf(stderr, "evutil_inet_ntop failed\n"); + return; + } +#endif +} + +} +} +}
\ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h new file mode 100644 index 0000000..7e8eaa9 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/libevent/EventIOProcessor.h @@ -0,0 +1,96 @@ +#ifndef EVENTIOPROCESSOR_H_2CUY93KU +#define EVENTIOPROCESSOR_H_2CUY93KU + +#include "uscxml/concurrency/eventqueue/libevent/DelayedEventQueue.h" +#include "uscxml/Interpreter.h" +#include "uscxml/Factory.h" +#include <sys/time.h> + +#include <event2/http.h> +#include <event2/http_struct.h> + +namespace uscxml { +namespace io { +namespace libevent { + +class EventIOServer; + +class EventIOProcessor : public uscxml::IOProcessor { +public: + struct SendData { + EventIOProcessor* ioProcessor; + uscxml::SendRequest req; + }; + + EventIOProcessor(); + virtual ~EventIOProcessor(); + virtual IOProcessor* create(uscxml::Interpreter* interpreter); + + virtual void send(uscxml::SendRequest& req); + virtual void invoke(uscxml::InvokeRequest& req); + virtual void cancel(const std::string sendId); + + std::string getURL() { return _url; } + void setURL(const std::string& url) { _url = url; } + + void start(); + static void run(void* instance); + + static void httpMakeSendReq(void* userdata, std::string eventName); + static void httpSendReqDone(struct evhttp_request *req, void *cb_arg); + static void httpRecvReq(struct evhttp_request *req, void *arg); + +protected: + std::map<std::string, SendData> _sendData; + + std::string _url; + + uscxml::DelayedEventQueue _eventQueue; + uscxml::Interpreter* _interpreter; + std::map<std::string, struct evhttp_connection*> _httpConnections; + std::map<std::string, struct evhttp_request*> _httpRequests; + struct evdns_base* _dns; + + friend class EventIOServer; +}; + +class EventIOServer { +private: + static EventIOServer* getInstance(); + EventIOServer(unsigned short port); + ~EventIOServer(); + + void start(); + void stop(); + static void run(void* instance); + + void determineAddress(); + static std::string syncResolve(const std::string& hostname); + + static void registerProcessor(EventIOProcessor* processor); + static void unregisterProcessor(EventIOProcessor* processor); + + + std::map<std::string, EventIOProcessor*> _processors; + + struct event_base* _base; + struct evhttp* _http; + struct evhttp_bound_socket* _handle; + + unsigned short _port; + std::string _address; + + static EventIOServer* _instance; + static tthread::recursive_mutex _instanceMutex; + tthread::thread* _thread; + tthread::recursive_mutex _mutex; + bool _isRunning; + + friend class EventIOProcessor; +}; + +} +} +} + +#endif /* end of include guard: EVENTIOPROCESSOR_H_2CUY93KU */
\ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp new file mode 100644 index 0000000..a62fefc --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.cpp @@ -0,0 +1,3 @@ +#include "uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h" +#include "uscxml/Message.h" + diff --git a/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h new file mode 100644 index 0000000..bb7a0fc --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/mongoose/MongooseIOProcessor.h @@ -0,0 +1,15 @@ +#ifndef MONGOOSEIOPROCESSOR_H_JS0GMSFO +#define MONGOOSEIOPROCESSOR_H_JS0GMSFO + +#include "uscxml/Interpreter.h" +#include "uscxml/Factory.h" + +namespace uscxml { + +class MongooseIOProcessor : public IOProcessor { + +}; + +} + +#endif /* end of include guard: MONGOOSEIOPROCESSOR_H_JS0GMSFO */ diff --git a/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp new file mode 100644 index 0000000..7aa9169 --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.cpp @@ -0,0 +1,74 @@ +#include "uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h" +#include "uscxml/Message.h" + +#include <pion/http/request.hpp> +#include <pion/http/message.hpp> + +namespace uscxml { + +using namespace pion; + +PionIOProcessor::PionIOProcessor() { +} + +PionIOProcessor::~PionIOProcessor() { + +} + +IOProcessor* PionIOProcessor::create(Interpreter* interpreter) { + PionIOProcessor* io = new PionIOProcessor(); + io->_interpreter = interpreter; + io->_ioServer = PionIOServer::getInstance(); + return io; +} + +void handle_connection(pion::tcp::connection_ptr& tcp_conn) { +} + +void PionIOProcessor::send(SendRequest& req) { + + boost::system::error_code error_code; + boost::asio::io_service io_service; + + pion::tcp::connection tcp_conn(io_service, 0); + error_code = tcp_conn.connect("localhost", 8080); + if (error_code) throw error_code; // connection failed + + + + http::request httpReq; + httpReq.set_method("POST"); + if (req.event.size() > 0) + httpReq.add_header("_scxmleventname", req.event); + + httpReq.send(tcp_conn, error_code); + +// http::request_writer writer; +// writer. + +} +void PionIOProcessor::invoke(InvokeRequest& req) { + +} +void PionIOProcessor::cancel(const std::string sendId) { + +} + +PionIOServer::PionIOServer() : pion::tcp::server(0) { +} + +PionIOServer::~PionIOServer() { +} + +void PionIOServer::handle_connection(pion::tcp::connection_ptr& tcp_conn) { +} + +PionIOServer* PionIOServer::_instance = NULL; +PionIOServer* PionIOServer::getInstance() { + if (_instance == NULL) { + _instance = new PionIOServer(); + } + return _instance; +} + +}
\ No newline at end of file diff --git a/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h new file mode 100644 index 0000000..154acdb --- /dev/null +++ b/src/uscxml/ioprocessor/basichttp/pion/PionIOProcessor.h @@ -0,0 +1,47 @@ +#ifndef PIONIOPROCESSOR_H_VAITDNCN +#define PIONIOPROCESSOR_H_VAITDNCN + +#include "uscxml/Interpreter.h" +#include "uscxml/Factory.h" +#include "uscxml/concurrency/DelayedEventQueue.h" + +#include <pion/http/request_writer.hpp> +#include <pion/http/request_writer.hpp> +#include <pion/tcp/server.hpp> + +namespace uscxml { + +class PionIOServer : public pion::tcp::server { +public: + PionIOServer(); + virtual ~PionIOServer(); + DelayedEventQueue _eventQueue; + + virtual void handle_connection(pion::tcp::connection_ptr& tcp_conn); + + static PionIOServer* getInstance(); + static PionIOServer* _instance; + + pion::http::request_writer_ptr _writer; + pion::tcp::connection_ptr _conn; + +}; + +class PionIOProcessor : public IOProcessor { +public: + PionIOProcessor(); + virtual ~PionIOProcessor(); + virtual IOProcessor* create(Interpreter* interpreter); + + virtual void send(SendRequest& req); + virtual void invoke(InvokeRequest& req); + virtual void cancel(const std::string sendId); + +protected: + Interpreter* _interpreter; + PionIOServer* _ioServer; +}; + +} + +#endif /* end of include guard: PIONIOPROCESSOR_H_VAITDNCN */ |