Master Chef has decided he needs a "Monthly Muffin" program to give out free muffins to loyal customers. To help prevent cheating, he's decided to go with the idea of sending a barcode to a customer that can then be scanned at the register. As I've mentioned, this is a high tech bakery. The barcode generator is a SOAP based web service and so uses XML as the data exchange format. Let's dive into the tools available for working with SOAP web services specifically, but XML in general.
When sending data from one web service to another, the data needs to be in a structured format so the receiving system can properly parse it out. It'sratherhardtoread a phrase with no spaces, ro a esarhp nittew drawkcab if you have been expecting something different. The same is true of web service calls. The xMatters APIs all accept JSON (JavaScript Object Notation) as that is quickly becoming the industry standard for sending data back and forth from one place to another on the web. JSON is great because it is self documenting, human readable, compact and most importantly, it is a javascript object! This makes referencing items a piece of cake if the receiving system parses the data with javascript. However, there are still a lot of web services, such as our barcode generator, that work in XML (eXtensible Markup Language). But never fear, the Integration Builder can receive anything (literal kitchen sink excluded) and process and read the data coming in.
The barcode generator service is found here. I did some searching and found a nice post that gave example inputs on how to call this web service. So to get a bar code, we need to generate an XML document that looks like this:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:web="http://www.webservicex.net/">
<soap:Header/>
<soap:Body>
<web:GenerateBarCode>
<web:BarCodeParam>
<web:Height>100</web:Height>
<web:Width>150</web:Width>
<web:Angle>0</web:Angle>
<web:Ratio>5</web:Ratio>
<web:Module>0</web:Module>
<web:Left>0</web:Left>
<web:Top>0</web:Top>
<web:CheckSum>true</web:CheckSum>
<web:FontName>Arial</web:FontName>
<web:BarColor>yellow</web:BarColor>
<web:BGColor>black</web:BGColor>
<web:FontSize>20</web:FontSize>
<web:barcodeOption>None</web:barcodeOption>
<web:barcodeType>CodeEAN8</web:barcodeType>
<web:checkSumMethod>None</web:checkSumMethod>
<web:showTextPosition>BottomRight</web:showTextPosition>
<web:BarCodeImageFormat>GIF</web:BarCodeImageFormat>
</web:BarCodeParam>
<web:BarCodeText>9783161484100</web:BarCodeText>
</web:GenerateBarCode>
</soap:Body>
</soap:Envelope>
I played with this in my SOAP UI client and the result will look something like this: (I cut out a large portion, you get the idea)
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GenerateBarCodeResponse xmlns="http://www.webservicex.net/">
<GenerateBarCodeResult>R0lGODlhlgBkAPcAAAAAAIAAAACAAICAAAAAgIAAgACAgICAgMDAwP8AAAD/AP//AAAA//8A/wD//////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMwAAZgAAmQAAzAAA/wAzAAAzMwAzZgAzmQAzzAAz/wBmAABmMwBmZgBmmQBmzABm/wCZAACZMwCZZgCZmQCZzACZ/wDMAADMMwDMZgDMmQDMzADM/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMzADMzMzMzZjMzmTMzzDMz/zNmADNmMzNmZjNmmTNmzDNm/zOZADOZMzOZZjOZmTOZzDOZ/zPMADPMMzPMZjPMmTPMzDPM/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YzAGYzM2YzZmYzmWYzzGYz/2ZmAGZmM2ZmZmZmmWZmzGZm/2aZAGaZM2aZZmaZmWaZzGaZ/2bMAGbMM2bMZmbMmWbMzGbM/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkA
...snip..
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</GenerateBarCodeResult>
</GenerateBarCodeResponse>
</soap:Body>
</soap:Envelope>
That GenerateBarCodeResult is a base64 encoded image, and running it through an online base64 image converter such as this one gives us a handy barcode!
Sweet! Now, how would we deal with this in our integration builder? Well, referencing the integration builder tools page here gives us a couple of different options. These are mostly for reading the result of an XML payload, but we are looking to generate an XML payload, so we'll use a few of these tools. I've written up a function that uses some of the items on that page and we'll go through it line by line.
/*
* generateBarcode
* Function to call the example barcode generator found at http://www.webservicex.net/WS/WSDetails.aspx?CATID=8&WSID=76
* textToEncode - The text to convert into a barcode. Note that some barcodes do not allow for actual text, and must be numbers
* returns - base64 encoded image
*/
function generateBarcode( textToEncode ) {
// First, generate the XML as a string. This is copied from my trusty SOAP UI tool
// We are also hard coding a lot of values here for simplicity.
var xmlStr = '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" ' +
' xmlns:web="http://www.webservicex.net/">' +
' <soap:Header/>' +
' <soap:Body>' +
' <web:GenerateBarCode>' +
' <web:BarCodeParam>' +
' <web:Height>100</web:Height>' +
' <web:Width>150</web:Width>' +
' <web:Angle>0</web:Angle>' +
' <web:Ratio>5</web:Ratio>' +
' <web:Module>0</web:Module>' +
' <web:Left>0</web:Left>' +
' <web:Top>0</web:Top>' +
' <web:CheckSum>true</web:CheckSum>' +
' <web:FontName>Arial</web:FontName>' +
' <web:BarColor>yellow</web:BarColor>' +
' <web:BGColor>black</web:BGColor>' +
' <web:FontSize>20</web:FontSize>' +
' <web:barcodeOption>None</web:barcodeOption>' +
' <web:barcodeType>CodeEAN128A</web:barcodeType>' +
' <web:checkSumMethod>None</web:checkSumMethod>' +
' <web:showTextPosition>BottomRight</web:showTextPosition>' +
' <web:BarCodeImageFormat>GIF</web:BarCodeImageFormat>' +
' </web:BarCodeParam>' +
' <web:BarCodeText>TEXTHERE</web:BarCodeText>' +
' </web:GenerateBarCode>' +
' </soap:Body>' +
'</soap:Envelope>';
// We need to update a value in the XML, so we'll turn it into an XML object
// to make it easier to reference the elements inside.
var doc = new XMLUtils.DOMParser().parseFromString( xmlStr );
// I always print a lot of stuff to the log. Makes for easier debugging.
// We're getting the tag called "BarCodeText" in the "web" namespace. The
// getElementsByTagName returns an array and buried in there is our data.
console.log( 'doc.getElementsByTagName("web:BarCodeText")[0].firstChild.data: ' + doc.getElementsByTagName("web:BarCodeText")[0].firstChild.data );
// Set the value to the text we want to encode. If there were other elements
// we wanted to update, we can do similar calls.
doc.getElementsByTagName("web:BarCodeText")[0].firstChild.data = textToEncode;
// Build the request with the headers and set the endpoint.
var req = http.request({
'endpoint' : 'Barcode Generator',
'method' : 'POST',
'headers' : {
'Content-Type' : 'application/soap+xml;charset=UTF-8;action="http://www.webservicex.net/GenerateBarCode"'
}
});
// Make the call!. Using the ''+ part forces the doc object to a string
var respRAW = req.write( '' + doc );
// Ok, now we get to play with the fun stuff.
// JXON converts the XML into a JSON object, which is way
// cleaner to deal with. Remember the result of the
// req.write returns a JSON object with headers and a body
// so we just parse out the body.
var jsObj = JXON.parse( respRAW.body );
// JXON puts everything to lowercase for some reason, so we
// have to reference the items as such.
console.log( jsObj['soap:envelope']['soap:body']['generatebarcoderesponse']['generatebarcoderesult'] );
// Probably a good idea to add some error checking here
// Return the base64 encoded image
return jsObj['soap:envelope']['soap:body']['generatebarcoderesponse']['generatebarcoderesult'];
}
Ok, breaking this down. The first part is to generate the XML. As noted this is copied from SOAPUI and such an tool is invaluable when working with SOAP (or REST!) web services. If you have no idea what the payload and result should look like, how are you going to write code that will make it look like that? Here is the general payload we'll build:
var xmlStr = '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" ' +
' xmlns:web="http://www.webservicex.net/">' +
' <soap:Header/>' +
' <soap:Body>' +
' <web:GenerateBarCode>' +
' <web:BarCodeParam>' +
' <web:Height>100</web:Height>'
// snip
Strings are great for humans reading, but terrible for code to read. It is just a bunch of text to a computer so manipulating specific items in the string of data is a pain. So our first step is to convert it into a "DOM" object, which is used all over the web in javascript for interacting with HTML pages. It is pretty low level, but it works.
var doc = new XMLUtils.DOMParser().parseFromString( xmlStr );
Then, we set the value of the text to encode:
doc.getElementsByTagName("web:BarCodeText")[0].firstChild.data = textToEncode;
The next few bits around making HTTP calls are covered in other articles, so I won't go into too much detail, for example here. After we submit the request and get the response back, we'll use one of our handy tools called "JXON". This will take an XML string and turn it into a JSON object. Makes it way easier to get information out.
var jsObj = JXON.parse( respRAW.body );
Note that in this transformation process, the XML element names are converted to lower case. I banged my head on the table and the wall for a while until I realized that. So be sure to reference them as such, or you'l get errors about trying to access an undefined function. The last bit is to extract just the bit we need. There's a lot of junk returned and we only care about the base64 encoded image, so we can use the JSON notation to dive into the (previously) XML structure to return only the base64 encoded image.
return jsObj['soap:envelope']['soap:body']['generatebarcoderesponse']['generatebarcoderesult'];
Now that we have the base64 encoded image, we'll need to add that to the message layout. There are a couple of ways to do this. 1) Link to the image hosted on a server or 2) Embed the image directly in the message. This article from Sendgrid has all the gory details on the pros and cons of these approaches, but in short a lot of email clients will block an embedded image, but hosting an image can add complexity. Since we'll be targeting the mobile client I'm going to go with embedding the image directly in the message. If we were more concerned about images in emails, we could look into the many image hosting services that expose an API for posting images. Alternatively, your corporate IT or marketing team might have something usable.
Some googling will show that embedding a base64 image is rather straightforward: it is just the img
tag with a special element. Like so:
<img src="data:image/gif;base64,BASE64STRINGHERE">
Perfect. We'll build out our message in the sweet message design tools and mark the "Show Source" to get access to the underlying HTML and stick our img tag in with the src element. Well, there is a small problem though...These base64 strings can be pretty big, particularly if the image is pretty big. Our barcodes aren't too big, but the resulting base64 string has been around 7000 characters in my testing. The properties in xMatters only allow for 2000 characters at a time. So how are we going to stick 7000 characters into a property? Well, we're going to use a little trick I like to call "concatentation". If you put a bunch of text items next to each other without any spaces, then to an HTML parser (such as an email client or the xMatters mobile app) it looks like one big piece of text.
We can take advantage of this and just use several properties. So we're going to do a couple of things:
- Create a handful of properties to hold the chunks
- Chop up the base64 string into chunks 2000 characters long
- Send the chunks along to the event
- Re-assemble them in the message template
For the first step, I've created 6 properties that have a logical naming convention. This convention makes it easy to set their values in the next step. Note that I've set the size for all of these to 2000.
Chopping up strings of known length is pretty easy, so for step #2, we use the array notation of a JSON object to dynamically reference the property names. I love javascript for this reason. Trying to do this in a strongly typed language like Java or C# would be a serious pain.
var barcodeBase64 = generateBarcode( '3322111' );
// Each property in xMatters can hold a maximum of 2000 characters.
var SIZE = 2000;
// We've only built out 6 properties, and each property can only hold 2000 characters,
// giving us a total length of 12000 characters so we quit if the bar code is too long
if( barcodeBase64.length > 6*SIZE ) {
return;
}
trigger.properties = {};
// Figure out how many properties we need
var numProperties = Math.ceil( barcodeBase64.length / SIZE );
// Loop through each property and chunk it up
for( p=1; p<=numProperties; p++ ) {
trigger.properties[ 'barcode_base64_' + p ] = barcodeBase64.substring( SIZE*(p-1), SIZE*p );
}
Ok, step #3 above is handled when we create the event and creating events in the Integration Builder is referenced elsewhere, for example here, so I won't go into detail about that.
Step #4 is re-assembling the chunks in the template. To do this we need to get under the hood a little. Crack open the Email/ Fax/ Push Message template. Add an image, but instead of putting in a URL, put something noticeable such as URLHERE.
That will add a broken image icon, but that's ok.
Drag each property to the bottom of the email template like so:
This will store the UUIDs of the properties in the HTML, so when we view the source, we can see how the system does the replacing. So hit the Show Source checkbox at the bottom and you'll see the raw HTML. Note the list of UUIDs enclosed in ${}
at the bottom:
Next, copy all those UUIDs, in order, and paste them over the URLHERE text. Prepend the mess with "data:image/gif;base64," to tell the mobile client that we have the data for the image right here and it is a gif encoded in base64. It should look like this:
Ok, now let's trigger our script and check out the results. Success!
Whew! That was a project, but we learned a lot. We learned the tools for manipulating XML and we even learned how to embed images in a mobile app.
Comments
0 commentsPlease sign in to leave a comment.