Sending mail from an XML/SOAP call (R6)

So a recent request about sending mail from other applications prompted me to write a quick and dirty LotusScript agent that could send an e-mail from an XML request. This was enough to get the developers going but it also opened up the dialog about standardizing XML calls around the organization. The method that I employed in my example was a bastardized version of XML-RPC with a little bit of SOAP mixed in, but now we are going to make these calls full fledged SOAP calls and we will do it on Domino R6.

I mention this because R7 now has some new classes to make SOAP design alot easier, but our company is not that hot and heavy for R7 and we are unlikely to see any upgrade in the next year. Our admins will need to see R7 get past the Rx.uhOh verion before moving.

Too be honest, my SOAP kung-fu is not strong enough to speak authoritatively on the topic, so these next few weeks are going to be a learning experience for me. If any of you lurkers (I know you are there) see me stating something wrongly, please step in and put me right.


First things first; SOAP is nothing more than XML in a particular format so very little of the previous code actually has to be re-written. The gist of the required changes will be to create a “controller” agent that can determine the method the user wants, and then load the required script libraries. This allows for a single URL to access all of your Domino based web services. So this controlling agent will need to parse through the inbound XML to find the method, and that is the portion that we will look at today.

First, let’s look at a simple SOAP request that will be used to request some directory information based upon the first few letters of an employees last name:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
      <getAddressBookNames>
        <starts_with>cros</starts_with>
      </getAddressBookNames>
    </soap:Body>
</soap:Envelope>

So we have the “Envelope” portion which is what it says it is, the wrapper for the call. The “xmlns” attribute is what’s called a NameSpace reference and it deserves an explanation of its own. Namespaces can be critical but are not always required. They exist so that you can mix and match different schemas and data types and not get naming conflicts.

For example, in Java we have Domino “Document” classes, and we have DOMParser “Document” classes, and if we did not have Java’s package hierarchy we would never be able to use the two side by side. As it is, we can create a new Domino Document object using its full package name like this:

lotus.domino.Document dominoDoc = db.createDocument;

then create the DOMParser Document like this:

org.w3c.dom.Document domDoc = DocumentBuilder.parse(java.io.inputstream);

XML namespaces allow us to use XML tags with the same names side by side. In our example, anytime a node name is prefixed by “soap”, it will be referring to the SOAP envelope schema that you can find at that URL.

So first we have the Envelope, and then we have the body. It is the XML within the Body that we need and typically the first element node within the body has the method name that we need. But there is a catch. Just because I used the “soap” prefix on my envelope and body does not mean everyone else will. That prefix is arbitrary and could be “foo” for all any SOAP client cares. So when I go and tell my parser to find the first element node in the Body, I need to first extract from the envelope tag what prefix the client is using on the Body tag. For this we use a very simple LS function:

Function getEnvelopePrefix(strInput As String) As String
	Dim intStart As Integer, intEnd As Integer
	intStart = Instr(strInput, "< ") + 1
	intEnd = Instr(strInput, ":")
	getEnvelopePrefix = Mid$(strInput, intStart, intEnd-intStart)
End Function

Using this function we can determine the Body prefix, which we use in our controller agent to find the first element node in the Body, and therefore find the method name the user is requesting. Here is my code for the controller agent called “ws”:

Sub Initialize
	On Error Goto errorCatch
	Dim s As New NotesSession
	Dim db As NotesDatabase
	Dim docCtx As NotesDocument
	Dim domParser As NotesDOMParser
	Dim nodeDoc As NotesDOMDocumentNode, nodeDocOut As NotesDOMDocumentNode
	Dim nodeNode As NotesDOMElementNode, nodeChild As NotesDOMNode
	Dim nodeList As NotesDomNodeList
	Dim streamOut As NotesStream
	Dim methodName As String, strInput As String, strOut As String
	Dim bSuccess As Boolean
	Dim x As Integer
	Dim ls As String
	Dim objRef As Variant
	Set db = s.CurrentDatabase
	Set docCtx = s.DocumentContext
	Set domParser = s.CreateDOMParser
	Set streamOut = s.CreateStream
	strInput = docCtx.Request_Content(0)
	bSuccess = False
	strOut = "invalid method name specified"
	Call domParser.parse(strInput)
	Set nodeDoc = domParser.Document
         ' grab list of user name nodes
	Set nodeList = nodeDoc.GetElementsByTagName(getEnvelopePrefix(strInput) + ":Body")
	If nodeList.NumberOfEntries > 0 Then ' if we have one
		Set nodeNode = nodeList.GetItem(1) ' grab the actual node
		If nodeNode.HasChildNodes Then ' check to see if it has values
			Set nodeChild = nodeNode.FirstChild
			While Not (nodeChild.IsNull)
				If nodeChild.NodeType = DOMNODETYPE_ELEMENT_NODE Then
					methodName = nodeChild.NodeName
				End If
				Set nodeChild = nodeChild.NextSibling
			Wend
		Else
			strOut = "Missing method name"
			Goto sendResponse ' we have a node with no values
		End If
	Else
		strOut = "Missing method name"
		Goto sendResponse ' we do not have a methodname node
	End If
	ls = | Use "| & methodName & |"|
        ' if we have an error at this point, it will be an invalid method name
	On Error Goto sendResponse
	Execute ls
	Exit Sub
sendResponse:
	On Error Goto errorCatch
	Set domParser = s.CreateDOMParser
	Set nodeDoc = domParser.Document
	Set nodeNode = setDefaultResponse(nodeDoc, strOut, False)
	Call domParser.setOutput(streamOut)
	Call domParser.Serialize 
	Print {content-type:text/xml; charset=utf-8}
	Print {< ?xml version="1.0" encoding="UTF-8"?>} + streamOut.ReadText
	Exit Sub
errorCatch:
	strOut = “Error in xmlrpc:init in line: ” & Erl() & ” error$: ” & Error$
	Goto sendResponse
End Sub

The only piece missing here is the “setDefaultResponse” function. We use that function to return errors to the client, but otherwise the controller agent simply loads the script library with the method name specified and the library takes care of the normal response. Here is the response function:

Function setDefaultResponse(nodeDoc As NotesDOMDocumentNode, strOut As String, bSuccess As Boolean) As 
NotesDOMElementNode
	On Error Goto errorCatch
	Dim nodeFault As NotesDOMElementNode, nodeFaultString As NotesDOMElementNode
	Dim nodeEnv As NOtesDomElementNode, nodeBody As NotesDOMElementNode
	Dim nodeText As NotesDomTextNode
	Set nodeEnv = nodeDoc.CreateElementNode("soap:Envelope")
	Call nodeEnv.SetAttribute("xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/")
	Call nodeDoc.appendChild(nodeEnv)
	Set nodeBody = nodeDoc.CreateElementNode("soap:Body")
	Call nodeEnv.appendChild(nodeBody)
	Set nodeFault = nodeDoc.CreateElementNode("soap:Fault")
	Call nodeBody.AppendChild(nodeFault)
	Set nodeFaultString = nodeDoc.CreateElementNode("faultstring")
	Call nodeFaultString.SetAttribute("xsi:type","xsd:string")
	Call nodeFault.AppendChild(nodeFaultString)
	Set nodeText = NodeDoc.CreateTextNode(strOut)
	Call nodeFaultString.AppendChild(nodeText)
	Set setDefaultResponse = nodeEnv
	Exit Function
errorCatch:
	Messagebox "Error in xmlrpc:setDefaultResponse on line: " & Erl & " - message:" & Error$()
	Exit Function
End Function

The thing to note here is the use of the Fault node when reporting errors in SOAP. The SOAP specification allows for four elements in the fault node; “faultcode”, “faultstring”,”faultactor”, and “detail”. These are pretty self-explanatory except for the “faultactor” node which is basically a way to tell the client if it was a server-side error, or an error with the call from the client.

Next post will be changing the old agent to a script library and formatting its output as a SOAP response. Other topics I’ll hit another day will be WSDL files, testing, and calls using more complex data types.

No Responses to “Sending mail from an XML/SOAP call (R6)”

  1. object Says:

    This is exactly what I was looking for, thanks for the great information.

Leave a Reply