// Called when an event occurs in the kernel void StringListener::OnKernelEvent(int eventIDIn, AgentSML* pAgentSML, void* pCallData) { // There are currently no kernel events corresponding to this SML event. // They are all directly generated from SML. If we later add kernel callbacks // for this class of events they would come here. smlStringEventId eventID = static_cast<smlStringEventId>(eventIDIn); StringListenerCallbackData* pCallbackData = static_cast<StringListenerCallbackData*>(pCallData); assert(pCallbackData); memset(pCallbackData->pReturnStringBuffer, 0, pCallbackData->maxLengthReturnStringBuffer); // Get the first listener for this event (or return if there are none) ConnectionListIter connectionIter ; if (!EventManager<smlStringEventId>::GetBegin(eventID, &connectionIter)) { return; } // We need the first connection for when we're building the message. Perhaps this is a sign that // we shouldn't have rolled these methods into Connection. Connection* pConnection = *connectionIter ; // Convert eventID to a string char const* event = m_pKernelSML->ConvertEventToString(eventID) ; // Build the SML message we're doing to send. soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_Event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamEventID, event) ; if (pCallbackData->pData) { pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamValue, pCallbackData->pData) ; } // Note: we should be telling the client the maximum length of the result, // however, we're planning on changing this so there is no maximum length // so we're not implementing this. // Send the message out AnalyzeXML response ; SendEvent(pAgentSML, pConnection, pMsg, &response, connectionIter, GetEnd(eventID)) ; char const* pResult = response.GetResultString() ; if (pResult != NULL) { // If the listener returns a result then take that // value and return it in "pReturnValue" to the caller. // If the client returns a longer string than the caller allowed we just truncate it. // (In practice this shouldn't be a problem--just need to make sure nobody crashes on a super long return string). strncpy(pCallbackData->pReturnStringBuffer, pResult, pCallbackData->maxLengthReturnStringBuffer) ; pCallbackData->pReturnStringBuffer[ pCallbackData->maxLengthReturnStringBuffer - 1 ] = 0 ; // Make sure it's NULL terminated } // Clean up delete pMsg ; }
// Execute the command line by building up an XML message and submitting it to our regular command processor. bool RhsListener::ExecuteCommandLine(AgentSML* pAgent, char const* pFunctionName, char const* pArgument, int maxLengthReturnValue, char* pReturnValue) { KernelSML* pKernel = m_pKernelSML ; // We'll pretend this came from the local (embedded) connection. Connection* pConnection = pKernel->GetEmbeddedConnection() ; // Build up a single command line from our functionName + argument combination std::stringstream commandLine; commandLine << pFunctionName; if (pArgument) { commandLine << " " ; commandLine << pArgument ; } // Build up a message to execute the command line bool rawOutput = true ; soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_CommandLine, rawOutput) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamAgent, pAgent->GetName()); pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamLine, commandLine.str().c_str()) ; AnalyzeXML incoming ; incoming.Analyze(pMsg) ; // Create a response object which the command line can fill in soarxml::ElementXML* pResponse = pConnection->CreateSMLResponse(pMsg) ; // Execute the command line bool ok = pKernel->ProcessCommand(sml_Names::kCommand_CommandLine, pConnection, &incoming, pResponse) ; if (ok) { // Take the result from executing the command line and fill it in to our "pReturnValue" array. AnalyzeXML response ; response.Analyze(pResponse) ; char const* pResult = response.GetResultString() ; if (pResult) { strncpy(pReturnValue, pResult, maxLengthReturnValue) ; pReturnValue[maxLengthReturnValue-1] = 0 ; } } // Clean up delete pMsg ; delete pResponse ; return ok ; }
char const* Agent::ConvertIdentifier(char const* pClientIdentifier) { // need to keep the result around after the function returns // bad static std::string kernelIdentifier; AnalyzeXML response; // Send the command to the kernel bool ret = m_Kernel->GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_ConvertIdentifier, GetAgentName(), sml_Names::kParamName, pClientIdentifier); if (ret) { // Get the result as a string char const *pResult = response.GetResultString(); if (pResult && strlen(pResult)) { kernelIdentifier.assign(pResult); return kernelIdentifier.c_str(); } } return pClientIdentifier; }
bool RhsListener::ExecuteRhsCommand(AgentSML* pAgentSML, smlRhsEventId eventID, std::string const& functionName, std::string const& arguments, std::string* pResultStr) { bool result = false ; // Get the list of connections (clients) who have registered to implement this right hand side (RHS) function. ConnectionList* pList = GetRhsListeners(functionName.c_str()) ; // If nobody is listening we're done (not a bug as we register for all rhs functions and only forward specific ones that the client has registered) if (!pList || pList->size() == 0) return result ; ConnectionListIter connectionIter = pList->begin() ; // We need the first connection for when we're building the message. Perhaps this is a sign that // we shouldn't have rolled these methods into Connection. Connection* pConnection = *connectionIter ; // Convert eventID to a string char const* event = m_pKernelSML->ConvertEventToString(eventID) ; // Build the SML message we're doing to send. // Pass the agent in the "name" parameter not the "agent" parameter as this is a kernel // level event, not an agent level one (because you need to register with the kernel to get "agent created"). soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_Event) ; if (pAgentSML) pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamName, pAgentSML->GetName()) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamEventID, event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamFunction, functionName.c_str()) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamValue, arguments.c_str()) ; #ifdef _DEBUG // Generate a text form of the XML so we can look at it in the debugger. char* pStr = pMsg->GenerateXMLString(true) ; #endif AnalyzeXML response ; // We want to call embedded connections first, so that we get the best performance // for these functions. I don't want to sort the list or otherwise change it so // instead we'll just use a rather clumsy outer loop to do this. for (int phase = 0 ; phase < 2 && !result ; phase++) { // Only call to embedded connections bool embeddedPhase = (phase == 0) ; // Reset the iterator to the beginning of the list connectionIter = pList->begin(); // Keep looping until we get a result while (connectionIter != pList->end() && !result) { pConnection = *connectionIter ; // We call all embedded connections (same process) first before // trying any remote methods. This ensures that if multiple folks register // for the same function we execute the fastest one (w/o going over a socket for the result). if (pConnection->IsRemoteConnection() && embeddedPhase) { connectionIter++ ; continue ; } // It would be faster to just send a message here without waiting for a response // but that could produce incorrect behavior if the client expects to act *during* // the event that we're notifying them about (e.g. notification that we're in the input phase). bool ok = pConnection->SendMessageGetResponse(&response, pMsg) ; if (ok) { char const* pResult = response.GetResultString() ; if (pResult != NULL) { (*pResultStr) = pResult ; result = true ; } } connectionIter++ ; } } #ifdef _DEBUG // Release the string form we generated for the debugger pMsg->DeleteString(pStr) ; #endif // Clean up delete pMsg ; return result ; }
bool RhsListener::HandleFilterEvent(smlRhsEventId eventID, AgentSML* pAgent, char const* pArgument, std::string &pReturnValue) { // Currently only supporting one event here, but that could change in time. assert(eventID == smlEVENT_FILTER) ; // Filters are handled as a RHS function call internally, using a special reserved name. char const* pFunctionName = sml_Names::kFilterName ; // Get the list of connections (clients) who have registered to implement this right hand side (RHS) function. ConnectionList* pList = GetRhsListeners(pFunctionName) ; bool result = false ; // If nobody is listening we're done (not a bug as we register for all rhs functions and only forward specific ones that the client has registered) if (!pList || pList->size() == 0) return result ; ConnectionListIter connectionIter = pList->begin() ; // We need the first connection for when we're building the message. Perhaps this is a sign that // we shouldn't have rolled these methods into Connection. Connection* pConnection = *connectionIter ; // Convert eventID to a string char const* event = m_pKernelSML->ConvertEventToString(eventID) ; // Copy the initial command line into the return buffer and send that over. // This will be sequentially replaced by each filter in turn and whatever // is left in here at the end is the result of the filtering. // This allows multiple filters to act on the data, each modifying it as it goes. // A side effect of this is that the order the filters work on the data is significant. // Anyone in the chain can set the string to be the empty string, which brings the // whole process to a halt (they have eaten the command line at that point). pReturnValue.reserve(strlen(pArgument) + 1); //adding 1 because strlen does not count the terminating null character pReturnValue.assign(pArgument); bool stop = false ; while (connectionIter != pList->end() && !stop) { // Build the SML message we're doing to send. // Pass the agent in the "name" parameter not the "agent" parameter as this is a kernel // level event, not an agent level one (because you need to register with the kernel to get "agent created"). soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_Event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamName, pAgent ? pAgent->GetName() : "") ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamEventID, event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamFunction, sml_Names::kFilterName) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamValue, pReturnValue.c_str()) ; // We send the current command line over #ifdef _DEBUG // Generate a text form of the XML so we can look at it in the debugger. char* pStr = pMsg->GenerateXMLString(true) ; #endif AnalyzeXML response ; pConnection = *connectionIter ; bool ok = pConnection->SendMessageGetResponse(&response, pMsg) ; if (ok) { char const* pResult = response.GetResultString() ; if (pResult != NULL) { // If the listener returns a result then take that // value and return it in "pReturnValue" to the caller. // If the client returns a longer string than the caller, pReturnValue is resized to fit it. pReturnValue.reserve(strlen(pResult) + 1); //adding 1 because strlen does not count the terminating null character pReturnValue.assign(pResult); result = true ; } else { // If one of the filters returns an empty string, stop the process at that point // because the command has been "eaten". Make the result the empty string. pReturnValue[0] = 0 ; result = true ; stop = true ; } } connectionIter++ ; #ifdef _DEBUG // Release the string form we generated for the debugger pMsg->DeleteString(pStr) ; #endif // Clean up delete pMsg ; } return result ; }