on 05-02-2012 04:52 AM
This is a collection of recipes I use(d) working with UCCX scripts.
Feel free to use them - I hope you will find them useful.
1. Check credit card number validity (Luhn algorithm)
2. Java version for the ultra lazy
3. Poor, but elegant man's way of sending emails (JavaMail)
4. Poor, less elegant man's way of sending emails (Sockets)
6. Measuring script execution time
7. Getting the prompt's length in seconds (G.711 uLaw)
8. Get enterprise variables without knowing their names
9. Store information in a properties file
We can use the Luhn algorithm to test whether a credit card number is valid.
For example, the customer may enter the credit card number using DTMF and before actually sending it to a backend database, you can check its validity.
Please note this algorithm does not check the existence of a credit card, only the validity of its number.
I am going to introduce two variables:
ccnumber - type String, initial value "" - this holds the credit card number
ccvalid - type byte, initial value (byte)0 - this is where the algorith reports the card's validity
Insert a new Set step into your script - the return variable is ccvalid, and the value is the following block of code:
{
int sum = 0;
boolean alternate = false;
boolean isValid = false;
try {
int i = 0;
for (i = ccnumber.length() - 1; i >= 0; i--) {
int n = Integer.parseInt(ccnumber.substring(i, i + 1));
if (alternate) {
n = n * 2;
if (n > 9) {
n = (n % 10) + 1;
}
}
sum += n;
alternate = !alternate;
}
isValid = (sum % 10 == 0);
} catch (Exception e) {
return -1;
}
return (isValid == true ? 1 : 0);
}
The value of ccvalid after this step will be:
If you have multiple UCCX installations and you want to check the Java version without opening the documentation (for example, you do not have internet connection or you are just being ultra lazy), you can use the following snippet to get the Java version - or, more precisely, the Java Development Kit (JDK) version for your UCCX.
Just for the demonstration, I am going to use a new variable: javaVersion, type String, initial value "".
Then a Set step, with the variable javaVersion, and the value will be this piece of code:
System.getProperty("java.version")
Insert this step into a new script, and then just press F10 (= Step Over) until the cursor goes past this step and watch the value of the javaVersion variable. It should be something like "1.6.0_17" - meaning it's JDK 6.
The step:
And the value of the javaVersion variable (this is UCCX 8.0):
Of course, it's easier to use the Create email step (provided you've got the correct UCCX licenses, at this moment it's Premium and IP IVR only), but Java gives you more control and more possibilities:
You can read a nice introduction about JavaMail on Wikipedia.
This example script sends a plain text email with a body containing non-ANSI characters (a sentence in Hungarian) to multiple addresses, with a blind carbon copy (Bcc:). Authentication is not required.
I am going to use these variables:
Please note, some variables are declared final, meaning you won't be able/don't need to change in within the script. It's a matter of choice.
And the steps
1. Setting the values of fromEmailAddress, fromEmailPersonal, recipientEmailAddress, and the emailSubject:
2. Then constructing the email body, and assign it to the emailBody variable. I am going to use a block of code here, concatenating different strings (with StringBuffer, one may also use StringBuilder):
{
StringBuffer myBuf = new StringBuffer();
String newLine = System.getProperty("line.separator");
myBuf.append("Dear business partner," + newLine);
myBuf.append("I hope your enjoyed our talk last night about project 'Árvíztűrő tükörfúrógép'. " + newLine + newLine);
myBuf.append("Sincerely," + newLine );
myBuf.append("Me.");
return myBuf;
}
Notice the usage of the "line.separator" system property - that's what makes your script universal.
3. And the most important part: constructing the email, and handing it over to the SMTP server:
{
// setting the context properties:
java.util.Properties props = new java.util.Properties();
props.put("mail.smtp.host", smtpHost);
props.put("mail.smtp.port", smtpPort);
// creating a new mail session:
javax.mail.Session session = javax.mail.Session.getInstance(props);
try {
// new MIME message, version 1.0:
javax.mail.Message message = new javax.mail.internet.MimeMessage(session);
message.setHeader("MIME-Version" , "1.0" );
// From: address, including the Personal part:
message.setFrom(new javax.mail.internet.InternetAddress(fromEmailAddress, fromEmailPersonal ));
// then the recipient (To:) of course, you can use multiple recipients with the same command
// read the JavaMail API
message.setRecipients(javax.mail.Message.RecipientType.TO, javax.mail.internet.InternetAddress.parse( recipientEmailAddress ));
// adding the Bcc: address:
message.setRecipients(javax.mail.Message.RecipientType.BCC, javax.mail.internet.InternetAddress.parse( bccAddress ));
// setting the Subject:
message.setSubject( emailSubject );
// setting the email body:
message.setText(emailBody);
// finally, sending the email
javax.mail.Transport.send(message);
} catch (Exception e) {
// something went wrong, bail out, return false:
return false;
}
// no exceptions raised, return true:
return true;
}
This step assigns the value of the emailSent boolean variable:
Also a screenshot, without comments:
This is what I got (my address being the recipient):
And, of course the person on the Bcc: got the same email too.
In case you don't want to / can't use JavaMail, you can still use sockets to send a simple email message.
I am going to reuse the same variables as in the previous recipe (for the various addresses, except for the Bcc one, and the email body). But this time, for sending the email this code is used:
{
try {
//creating a new socket is easy:
java.net.Socket socket = new java.net.Socket(smtpHost, smtpPort);
// this might be unnecessary, but it's always good to set the buffer size:
socket.setReceiveBufferSize(60000);
// again, it's very important to be universal and not to use platform specific line separator strings like \r\n (Windows) or \n (Unix):
String separator = System.getProperty("line.separator");
// a new Reader and Writer represents the communications' input and output channel:
java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(socket.getInputStream()));
java.io.PrintWriter writer = new java.io.PrintWriter(new java.io.OutputStreamWriter(socket.getOutputStream()));
// this is required by some SMTP servers (Postfix in my case): reading the first line before anything is sent. Your SMTP server might not require this:
String smtpLine = "";
smtpLine = reader.readLine();
// say EHLO:
writer.write("EHLO localhost" + separator);
writer.flush();
// mail from:
writer.write("MAIL FROM:<" + fromEmailAddress + ">" + separator);
writer.flush();
// rcpt to:
writer.write("RCPT TO:<" + recipientEmailAddress + ">"+ separator);
writer.flush();
// and finally, the email itself:
writer.write("DATA" + separator);
writer.flush();
writer.write("From:" + fromEmailPersonal + " <" + fromEmailAddress + ">" + separator);
writer.flush();
writer.write("Subject:" + emailSubject + separator);
writer.flush();
writer.write(emailBody + separator);
// this is where we send it:
writer.write(separator + "." + separator);
writer.flush();
// closing the door:
writer.write("QUIT" + separator);
} catch (Exception e) {
// something went wrong, bail out, return false:
return false;
}
return true;
}
Obviously, since we are not reading anything, we don't now what the SMTP server tells us (for instance, it might not be allowed to relay for that particular recipient). You can use reader.readLine() to read from the InputStream, just to enhance the above very simplistic script.
And, this is the email I got this time (me being the recipient):
Why do we need them?
There are actually two more ways. Two examples:
Starting JDK 5, we can use the UUID class, which produces an "immutable universally unique identifier". Like this (provided you've got a String type variable named uuidRandomString):
Set uuidRandomString = java.util.UUID.randomUUID()
This will generate a string like "cffd0927-6d16-40e0-a2b8-27441e53cd3a"
This should work with JDK < 5 as well, even though there's a tiny little chance the result won't be unique. It's basically getting the actual date and time down to milliseconds, formatting it, and then attaching a random number from the 0..9999 range (this example uses a variable named simpleRandomString, of type String):
Set simpleRandomString =
{
String DATE_FORMAT="yyyyMMdd_HHmmss-SSS";
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat(DATE_FORMAT);
String sNow = sdf.format(new Date());
java.util.Random random = new java.util.Random();
return sNow + "X" + random.nextInt(9999).toString();
}
And it should generate a random string like "20120502_212917-340X3844".
If you ever wanted to know how long does it take to execute certain steps / (almost) the whole script, then this might be a good way to measure it.
For instance, if there's an expensive operation (like database reads and writes) you can see the amount of time it takes - down to nanoseconds.
For this demonstration, I am going to use three variables, all of type long:
We are going to use the System.nanoTime() method (available since JDK 5) which gives more precise information than System.currentTimeMillis().
Set startNanos = System.nanoTime()
/* series of expensive operations */
Set endNanos = System.nanoTime()
Set executionTimeNanos = endNanos - startNanos
The value of executionTimeNanos is roughly the amount of nanoseconds that took the "series of expensive operations" to run.
For example, executionTimeNanos = 5291900000L means 5.292 seconds.
This comes useful if
.. and you're interested in the length of the prompt.
Why?
It's important to get the prompt's (audible) length before certain steps, for example, you might want to refuse extremely long prompts (like 5 minutes) or you might want to decide to save wave files created by the Recording step elsewhere (e.g. short prompts to a database, large prompts to a file system).
I am going to use two variables:
myPrompt - type Prompt, initial value P[] which is the default - this is the Prompt you just downloaded or recorded;
promptLength - type float, initial value 0.0F (default) - this will be the prompt's length.
For this demostration's sake, pretend, the myPrompt has already got its value - I am going to use a prompt that is in the prompt repository, named busy.wav (it's just a regular G.711 uLaw wave file)
For the calculation, use a Set step, where the return variable will be promptLength and the value will be this piece of code:
{
try {
Document doc = (Document) myPrompt;
javax.sound.sampled.AudioInputStream ais = javax.sound.sampled.AudioSystem.getAudioInputStream(doc.getInputStream());
int avail = ais.available();
return avail / 8000f;
} catch (Exception e) {
return -1;
}
}
The value of promptLength will be -1 if an error occured, in other cases it will be the prompt's length in seconds, e.g. 12.928F meaning 12 seconds and 928 milliseconds.
It works with G.711 uLaw only - javax.sound.sampled.* cannot work with G.729 audio streams (I guess that's because G.729 is covered by an unfriendly patent).
This script also assumes you feed it with a prompt UCCX can work with, so it's 8000 frames per second and 1 bytes per frame, and of course 1 channel (mono).
Example:
And the prompt's length is roughly 13 seconds:
This works in a UCCE environment, where UCCX is actually IP IVR.
You can get the enterprise variables (PeripheralVariable 1..10 and Expanded Call Context aka ECC variables) - those sent and expected by the ICM - easily, without knowing their names.
For instance, you may want to check if all expected ECC variables arrived from ICM. Or, you just want to iterate through all variables, looking for a needle in a haystack, for a specific value within the set of values of all PeripheralVariables and ECC's.
In this example, I already set up everything necessary on the ICM side, there is an ICM script, which sets a couple of variables and then sends the call to our UCCX script which is discussed here.
We will use four variables:
contact - type Contact - initial value null - this is where we store the contact object,
expVariables - type java.util.HashMap - initial value null - this HashMap will store our ECC variables,
s - type String - initial value "" - just for debugging purposes,
variables - type String[] - initial value null - this String array will store our "regular" PeripheralVariables plus other useful stuff.
First, we need to get the contact object. We use the Get Trigger Info step for it:
Next, let's get the "regular" variables, including PeripheralVariable 1..10 and some other useful information. We use a String[] (String array) named variables and a Set step for this:
Set variables =
{
com.cisco.call.CallContact callContact = (com.cisco.call.CallContact) contact;
com.cisco.call.CallDataContextMap contextMap = com.cisco.call.CallDataContextMap.instance();
com.cisco.call.CallDataContext callDataContext = contextMap.get(callContact.getImplId().toString());
return callDataContext.getVariables();
}
Now, if we run reactive debugging, we see the variable variables populated with the following values:
new String[] {"jstest.aef", "config param", "072512345678", "55", "", "", "", "", "", "", "peripheralVariable7", "", "", "", null, null}
Remember, it's an array, so it's an ordered list of String values. What are those values?
index | value |
---|---|
0 | The name of the external script triggered by ICM - it's basically the handle we use in ICM to trigger our UCCX script. |
1 | This is the Configuration Parameter setting in the ICM Configuration Manager, Network VRU Script List. |
2 | ANI. |
3 | CED. |
4 | PeripheralVariable1 |
5 | PeripheralVariable2 |
6 | PeripheralVariable3 |
7 | PeripheralVariable4 |
8 | PeripheralVariable5 |
9 | PeripheralVariable6 |
10 | PeripheralVariable7 |
11 | PeripheralVariable8 |
12 | PeripheralVariable9 |
13 | PeripheralVariable10 |
14 | ?? |
15 | ?? |
As we can see, variables[10] - PeripheralVariable7 - has a value of "peripheralVariable7". This is what I have set in my ICM script:
The next step will be used to harvest the ECC variables (unfortunately, this works only for simple ECC's, not for arrays). We will have the expVariables variable - of type java.util.HashMap - to store our ECC variables.
Set expVariables =
{
com.cisco.call.CallContact callContact = (com.cisco.call.CallContact) contact;
com.cisco.call.CallDataContextMap contextMap = com.cisco.call.CallDataContextMap.instance();
com.cisco.call.CallDataContext callDataContext = contextMap.get(callContact.getImplId().toString());
return callDataContext.getExpVariables();
}
Again, if we do reactive debugging, we can see our expVariables as:
java.util.HashMap?{user_prompt=rb_friday.wav, user_mail=pig@sty.com}
It's perfect, because this is what we set in the ICM script:
If you are new to Java, a HashMap is a list of key-value pairs. In this example, we have to pairs:
key: user_prompt, value: rb_friday.wav
key: user_mail: value: pig@sty.com
Notice the difference between the ICM notation and the UCCX on: Call.user_prompt becomes simply user_prompt, Call.user_mail becomes user_mail.
You may want to ask: what if we use the Set Enterprise Call Info Step before running the above code? The CallDataContext is updated immediately, so the above code will reflect the change. In fact, I set the value of CED - aka Call.CallerEnteredDigits - in UCCX, not in ICM, using a Set Enterprise Call Info step. And it appeared in the variables String[].
Hint: if you are new to Java, this is an example of iterating over the keys and values of a HashMap. The following piece of code simply creates a String like this:
"user_prompt: rb_friday.wav; user_mail: pig@sty.com; "
We use the following code in a Set step, resulting the value of the variable named s, type String:
Set s =
{
StringBuffer buffer = new StringBuffer(50);
java.util.Iterator entries = expVariables.entrySet().iterator();
while (entries.hasNext()) {
java.util.Map.Entry entry = (java.util.Map.Entry) entries.next();
String key = (String) entry.getKey();
Object value = (Object) entry.getValue();
buffer.append(key + ": " + value + "; ");
}
return buffer.toString();
}
If you happen to know how to get the ECC arrays, please let me know.
You don't need XML if you wan to store a small set of key-pair values. The Properties Java class gives you simple tools for storing and loading information from a human-readable text file. For instance:
It cannot be simpler. We have a comment ("This is just a comment in the properties file."), a timestamp ("Mon Jun 03 17:48:26 CEST 2013") when this file was created, and two key-pair values: "closed" set to "yes", and "reason" set to "weather".
With a few lines of Java code, we can easily create such files and also read their contents.
Notice: this is a UCCX 7.0SR5 I am demonstrating on, so I have the possibility of writing the file directly onto the local disk, with the"Write Document" step. With 8.0 and higher, this is not possible, but you may use steps like "Upload Document" or take a look at the documents about writing files on a Windows share or using SFTP or FTP.
I am going to use two variables:
filename - type String - initial value "c:/temp/cc-config.properties"
s - type String - initial value: ""
Add a Set step, variable s, and for the value, use this code block:
{
java.util.Properties props = new java.util.Properties();
props.setProperty("closed", "yes");
props.setProperty("reason", "weather");
String comments = "This is just a comment in the properties file.";
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
props.store(baos,comments);
return baos.toString();
}
Then, add a Write Document, or an Upload Document step, with the Document set to (Document) s, and the Filename (in the case of the Write Document step): fileName.
What happened here?
In the Set step, I created a new Properties object, named props. Set the property of "closed" to "yes", and also set the property of "reason" to "weather".
I also created a new String, named comments, with the following value: "This is just a comment in the properties file.".
Then I created a buffer, of type ByteArrayOutputStream and sent my props object there. This serialized it.
Finally, I wrote this serialized object (it's actually a String) to a file.
The result is a file named cc-config.properties, in c:\temp, with the following contents:
#This is just a comment in the properties file.
#Mon Jun 03 18:12:34 CEST 2013
closed=yes
reason=weather
Here's a screenshot of this "setter" script:
Added the following variables to a new script:
closed - type String - initial value: ""
reason - type String - initial value: ""
exitCode - type int - initial value: 0
doc - type Document - initial value DOC[]
fileName - type String - initial value: "c:/temp/cc-config.properties"
Then, added a Set step - it just loads the file into the memory. Since it is a file document, I use the Set doc = FILE[fileName] construct. If it were a document in the Document Repository of the UCCX, I would have used something like this: Set doc = DOC[documentName]
One simple Set step would read the properties from the file, assigning the values to the local variables (closed, reason), and alternatively, inform us whether the read operation was successful or not: if the exitCode value is -1, something must have happened (e.g. file does not exist or not correctly formatted), otherwise, if 0, everything went just alright.
Set exitCode = {
java.util.Properties props = new java.util.Properties();
try {
props.load(doc.getInputStream());
closed = props.getProperty("closed");
reason = props.getProperty("reason");
} catch (Exception e) {
return -1;
}
return 0;
}
What happens here: first I create a new, empty Properties object, named props. Then take the InputStream from the doc variable (it's like opening a bottle), and load the properties, using the load method. Next, I get the "closed" property and assign its value to the local closed variable. Same with the property "reason". Finally, I return either -1 if an exception (~ error) occured, or 0 if it did not. As the following screenshot demonstrates it, the values of the variables are set, according to the properties file:
Also, a screenshot of the "getter" script:
It's not working on UCCX 10.6 too. The problem is RecipientType and for now I can't resolve it or find workaround. Here is the log fragment (always same error), full log attached
=== cut here ===
446710: Aug 15 11:58:32.466 MSK %MADM-SCRIPT_MGR-3-UNABLE_LOAD_SCRIPT:Unable to load script: Script=PC/TestEMail.aef,Exception=java.io.InvalidClassException: javax.mail.Message$RecipientType; local class incompatible: stream classdesc serialVersionUID = -7479791750606340008, local class serialVersionUID = 8926476023043427979
446711: Aug 15 11:58:32.467 MSK %MADM-SCRIPT_MGR-3-EXCEPTION:java.io.InvalidClassException: javax.mail.Message$RecipientType; local class incompatible: stream classdesc serialVersionUID = -7479791750606340008, local class serialVersionUID = 8926476023043427979
=== cut here ===
Same error on UCCX 10.6 - script is perfectly running and working(!) from UCCX Editor, but failed to load into application. See my previous comment
Hi Gergely,
Really nice Job. I found very helpful the first code for Credit Card Validation.
Hope that you continue making great collaborations.
Great work on all of these...very helpful.
A note on #7 - the javax.sound.sampled.AudioSystem.getAudioInputStream() method requires an input stream source that supports mark/reset (AKA stream navigation). Recorded prompts in UCCX, when using the .getInputStream() method, do not support mark/reset. I had to modify the code to utilize a java.io.BufferedInputStream as so:
{
try {
Document Recording = (Document) DP[5000]; // Only here to get a Document for sample
java.io.BufferedInputStream bis = new java.io.BufferedInputStream(Recording.getInputStream());
javax.sound.sampled.AudioInputStream ais = javax.sound.sampled.AudioSystem.getAudioInputStream(bis);
int avail = ais.available();
return avail / 8000f;
} catch (Exception e) {
return -1;
}
}
EDIT: Forgot to mention, this was on UCCX 11.6(2).
Thanks!
Ryan
Hello, Could you please help me.
This code is returning null in UCCX Editor but working in Eclipse, it returns an array of string. I should take anything in consideration while using this code inside UCCX? MyArray is defined as array of string from UCCX side.
javax.xml.parsers.DocumentBuilder builder = javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder();
org.xml.sax.InputSource src=new org.xml.sax.InputSource();
src.setCharacterStream(new java.io.StringReader(s));
org.w3c.dom.Document doc = builder.parse(src);
org.w3c.dom.NodeList nodeList = doc.getElementsByTagName("Data1");int nl=nodeList.getLength();
javax.xml.xpath.XPath xpath =javax.xml.xpath.XPathFactory.newInstance().newXPath();
javax.xml.xpath.XPathExpression expr = xpath.compile("//Data1/Data2/Data3");
org.w3c.dom.NodeList List = (org.w3c.dom.NodeList) expr.evaluate(doc, javax.xml.xpath.XPathConstants.NODESET);
for (i = 0; i <=nl-1; i++) {
org.w3c.dom.Node node = List.item(i);
MyArray[i]=node.getTextContent();
}
return MyArray;
Hello, we have a certificate installed on a soap server and we are trying to connect to it using java and uccx editor,
I would like to know if it's possible to do it using java.
When we added the certificate files to the uccx it returned handshake failed exception.
Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: