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:
excellent effort, thanks.
Gergely, thank you so much for this amazing article! I somehow never realized until now that so much Java code could be embedded in a Set step. This is huge for me!
Hi David, thanks.
G.
Hi Gergely,
Could you please let me know what is the configuration required on IPIVR and ICM in order to send and receive ECC variables between ICM script and IPIVR script.
Thanks.
Hi,
I used the java code of point 3 to send email from UCCX script. The script used in version 7.x worked perfectly, but the same exact code used in versione 9.x cannot be load into application due to an InvalidClassException. The editor validates the script but when I try to load it into application the error is raised.
Any idea of the cause?
Filippo
Hi,
would you please post the exact error message. Thanks.
G.
Really nice stuff here,
can somebody explain me what is "fromEmailPersonal"
I am not sure to understand this one
Thanks
Hi,
RFC 2047 contains the answer. In Simple English, email addresses may contain a presonal name (in this case, the sender's name) and the email address. Like this: Anakin Skywalker <askywalker@force.net> where Anakin Skywalker is a "friendly", "human readable" name, and askywalker@force.net is the email address itself.
The JavaMail API simplifies this with the InternetAddress type.
G.
Thanks !!!!
Guys, I have UCCX Enhanced, I am trying the " Poor, but elegant man's way of sending emails (JavaMail) " but I have a little issue.
In the script editor the "tool / Validate " return Succeeded !
But, when I try to associate the script to an application I get an error that tell me to check my logs..
To test, in the script If I remove the set emailbody and set emailsent I can associate the script .. so there error is there...
It's the fist time I am playing with that kind of java... is there any requirement before ?
If I am trying the " 4. Poor, less elegant man's way of sending emails (Sockets)"
I can associate the script to the application but I can't call the application, i can't even run a debug ..
I am wondering if it is working on CCX v9 Enhanced.. !?
I think I hit the issue that Mr. "Filippo Tosti" tell us here in the forum.
Regards,
Hi,
did you check the logs? Is there anything suspicious?
I did not test it with CCX 9 yet (but with 7.x and 8.x both were OK).
G.
Hi Gergely,
Thanks for this very useful article. Based on your examples I was able to calculate the length in seconds of a g729 prompt. Maybe it is not 100 percent punctual, because of the file headers, but it is ok for me:
{
try
{
Document doc = (Document) Recording;
java.io.InputStream is = (java.io.InputStream) doc.getInputStream();
int avail = is.available();
return avail / 1000;
}
catch (Exception e)
{
return -1;
}
}
Regards,
Béla
Hi Gergely,
for my understanding to use java code in UCCX i copied the code from 1.) and modiefied them a little bit to my reqiurements. The only reqiurement is, give the script a parameter (here the credit card number and return the result to UCCX).
Following the code:
###############################################################################
public class ClassCheckCC {
public static void main (String args[]) {
// TODO Auto-generated method stub
String ccnumber = args[0];
//String ccnumber = "66666666666666666";
int isValid=99;
isValid=verifyCC(ccnumber);
System.out.println(isValid);
}
private static int verifyCC(String ccnumber){
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);
}
}
###############################################################################
In Eclipse i create a new java project named "CheckCreditCardNumber" and inserted this code into the new project. The name of the new class is "ClassCheckCC". Than i created a executable jar file "CheckCCNumber1.jar". This will run a expected in a DOS box. I call him in this manner: java -jar CheckCCNumber1.jar 1234567890123456
The number represents a credit card number.
From my opinion, always ready to implement it into UCCX.
JAR file uploaded to UCCX, moved on the right side (to Selected Classpath Entries:). Some service restarted. Do some tries to access the file and can see it in the UCCX editor.
Next i created a new script file and inserted a set step
the following error occures:
But the expected result from java code from type byte(-1,0, or 1). Shit happens, i declared the variable isValid as string:
One more try to call the java code:
At next the error message is different:
So, my question. What´s wrong? Where can i successful call this little java code.
I know, it´s possible to execute the code without additional jar files. For future purposes, i am interessted to implement more java classes into a jar file and this is a first step.
Greetings Maic Naatz
Gergely,
Great thread on how to do many things. We are just starting to do some outbound dialing. I'd like to know when a TTS prompt I'm generating actually contains a prompt or is just blank. During testing, with the server up or down I still get a prompt. Your testing of the length above appears to work for one of my uploaded prompts. But, when it is a TTS prompt, I always get back 0.0. Can you show an example of how you would test the lengh of a TTS prompt? If this is as simple as changing a file on the TTS server to put out the correctly formated codec, please let me know.
Thanks,
Bill
Bill, this is an excellent question. This must be caused by an internal feature of UCCX. I am not a hundred percent positive about this but I suspect that TTS prompts are originated at a different source not the UCCX itself, and this causes a seemingly zero length stream.
Can you please post this question in the Discussions section, someone might have a more specific explanation.
G.
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: