How to parse a string in CVP 7.0(1). Is there a built-in java class, other?

Answered Question
May 25th, 2010

Hi,


I need to parse,in CVP 7.0(1), the BAAccountNumber variable passed by the ICM dialer.  Is there a built-in java class or other function that would help me do this?


Our BAAccountNumber variable looks something like this: 321|XXX12345678|1901|M. In IP IVR I use the "Get ICM Data" object to read the BAAccountNumber variable from ICM and then I use the "token index" feature to parse the variable (picture below).

GetICMData.jpg


Alternately, IP IVR also has a Java class that allows me to do this; the class is "java.lang.String" and the method is "public int indexOf(String,int)"


Is there something equivalent in CVP 7.0(1)?



thanks

Yes, try #2. It will give you the most flexibility and you will learn how to make custom reusable action elements. The Java template to start with is as follows:


//These classes are used by custom configurable elements.
import com.audium.server.voiceElement.ActionElementBase;
import com.audium.server.voiceElement.ElementInterface;
import com.audium.server.voiceElement.Setting;
import com.audium.server.voiceElement.ElementData;
import com.audium.server.voiceElement.ElementException;

// This class is used by action elements.
import com.audium.server.session.ActionElementData;

/**
* This is the skeleton of a configurable action element. This is different
* from a standard action in that it is pre-built and the developer
* configures it in Audium Builder for Studio. The methods implemented here
* apply primarily to define the configuration for display in the Builder. Note
* that there is no need to implement the methods getExitStates because action
* elements only have a single exit state: "done".
*/
public class MyConfigurableAction extends ActionElementBase implements ElementInterface
{
    /**
     * This method is run when the action is visited. From

     * the ActionElementData object, the configuration

     * can be obtained.
     */
    public void doAction(String name, ActionElementData data)

                         throws ElementException
    {
  // PUT YOUR CODE HERE.
    }

/**
  * This method returns the name the action element will have in the Element
  * Pane in the Audium Builder for Studio.
  */
    public String getElementName()
    {
  // PUT YOUR CODE HERE.
    
        return "My action element";
    }

/**
  * This method returns the name of the folder in which this action element
  * resides. Return null if it is to appear directly under the Elements
  * folder.
  */
    public String getDisplayFolderName()
    {
  // PUT YOUR CODE HERE.
    
        return "My action element folder";
    }

/**
  * This method returns the text of a description of the action element that
  * will appear as a popup when the cursor points to the element.
  */
    public String getDescription()
    {
  // PUT YOUR CODE HERE.
    
        return "My action element description";
    }

/**
  * This method returns an array of Setting objects representing all the
  * settings this action element expects. Return null if the action element
  * does not need any settings.
  */
    public Setting[] getSettings() throws ElementException
    {
  // PUT YOUR CODE HERE.
    
        return null;
    }

/**
  * This method returns an array of ElementData objects representing the
  * element data that this action element creates. Return null if the action
  * element does not create any Element Data.
  */
    public ElementData[] getElementData() throws ElementException
    {
  // PUT YOUR CODE HERE.
 
        return null;
    }
}


Regards,

Geoff

There are three choices:


1. in ICM - use the substr(s, start, len) function in ICM to pull out what you need as long as the positions are fixed. If the positions are not fixed, you can use the find() function to move the pointer along, but it's trickier. Nevertheless, ICM can probably pull apart the BAAccountNumber and then pass it to CVP as name=value pairs in ToExtVXML[] args - pass in Entry_ID,Member_ID etc. CVP creates Session Variables with the name automatically.


2. Build a custom CVP VXML Action element - build a new action element and add it to Studio. This has Java code to parse the string and set Session variables. Build a parallel class to do the actual parsing, testing it with various inputs. Then copy the code into your action element. I like this method because you can test it.


3. Add in-line Java code to your existing CVP VXML processing. This is nice and compact, but is only possible to test in the context of a call. The code is carried over when you deploy, so that's a good thing. If it's really simple and you can write a good set of test code, then you can copy it in. I tend to prefer 2.


I would look at 1 first.


Regards,

Geoff

  • 1
  • 2
  • 3
  • 4
  • 5
Overall Rating: 5 (2 ratings)
Loading.
Correct Answer

There are three choices:


1. in ICM - use the substr(s, start, len) function in ICM to pull out what you need as long as the positions are fixed. If the positions are not fixed, you can use the find() function to move the pointer along, but it's trickier. Nevertheless, ICM can probably pull apart the BAAccountNumber and then pass it to CVP as name=value pairs in ToExtVXML[] args - pass in Entry_ID,Member_ID etc. CVP creates Session Variables with the name automatically.


2. Build a custom CVP VXML Action element - build a new action element and add it to Studio. This has Java code to parse the string and set Session variables. Build a parallel class to do the actual parsing, testing it with various inputs. Then copy the code into your action element. I like this method because you can test it.


3. Add in-line Java code to your existing CVP VXML processing. This is nice and compact, but is only possible to test in the context of a call. The code is carried over when you deploy, so that's a good thing. If it's really simple and you can write a good set of test code, then you can copy it in. I tend to prefer 2.


I would look at 1 first.


Regards,

Geoff

Voiceops SSC Tue, 05/25/2010 - 13:44

Geoff,

     Thanks for the reply.

I think I'll give number 2 a try since I have a bunch of campaigns that I will need to parse the BAAccountNumber for and that way I can write something they all can use.

Number 3 sounds interesting but I don't believe I'm there yet with CVP.

Number 1 looks too hairy!


thanks

Doan Khanh Tan Thanh Tue, 05/25/2010 - 19:19

Hi Geoff,


My outbound dialer IVR page use CVP. ICM version 7.2.7.


1. Dialer check for customer to be dialed out.

2. Dialer make a SIP call to dial out via CCM then SIP proxy.

3. When customer answer the call, dialer transfer this call to CVP.


My question is how can I transfer the BAAccountNumber via SIP (or a callID). I mean, when dialer transfer the customer call to CVP, it also sent the BAAccountNumber to CVP so that CVP can process this call based on this BAAccountNumber.


Regards,


Thanh

Doan Khanh Tan Thanh Tue, 05/25/2010 - 23:23

Thank you for the reply.


But I want to ask how can we transfer this BAAccountNumber along with the call.


First dialer know the number to dial out, then it makes SIP call to dial out. Then when customer answer, dialer will transfer the call to CVP.

So how can CVP know the BAAccountNumber.


Regards,


Thanh

Doan Khanh Tan Thanh Tue, 05/25/2010 - 23:33

Hi Geoff,


The problem is that dialer select a customer to dialout and transfer to ICM script (then ICM script will send to VRU). But how can ICM script know that which customer and account number that dialer dialed before. Let say, the BAAccountNumber is the key to distingquish the call from another.


Regards,


Thanh

Doan Khanh Tan Thanh Tue, 05/25/2010 - 23:44

To select a customer to dial out, there's a internal process between campaign manager and dialer. After dialer dial out and customer answer the call, it transfer to ICM script then ICM script treat this call as a new call. My problem is how can we distinguish the call based on BAAccountNumber or some possible callID.


Regards,


Thanh

Are you saying to me that it doesn't work? Or are you asking me how it works?


The BAAccountNumber is configured in the import file, and so is imported into the Campaign Manager, parsed through the rules and now established in the Dialing_List. The Dialer is going to grab a number of these at one time, marked as "P" for Pending by the Campaign Manager, and stored in memory in the Dialer. The Dialer attaches the BAAccountNumber as an ECC variable to the call and calls a customer number. Maybe a few don't answer, then finally someone answers.


ICM has to get this across to another peripheral - CVP. In the old model (before the type 10 VRU), you had to do a trans route to get the data across. Now you don't need to do that, because ICM can use the correlation ID style of trans route to move the data with the call behind the scenes.


Regards,

Geoff

Doan Khanh Tan Thanh Wed, 05/26/2010 - 05:02

What is I trying to say is that dialer will make the call first before transfer customer call to ICM script. Let me give you a example:


1. I have a list of customer number like this to import to ICM:


Number,BAAccountNumber

090888,12345

098767,12346

098777,12347


I have another Database (external) to store what to play to customer when transfer customer call to CVP


BAAccountNumber,MessageToPlay

12345,"Hello.wav"

12346,"Welcome.wav"

12347, "cisco.wav"


2. If dialer transfer to CVP this BAAcountNumber, CVP can process for further treatment like playing different audio file for differnt customer.


As you said, we will send the BAAcountNumber by send the ToEVXML variable. But as I mention before the dialer dial out first then transfer to ICM script (ICM will sent the call to CVP) but how can ICM script know what BAAccountNumber dialer choose before transfer this call to ICM script.



That's what I'm trying to say.


Thanh

Voiceops SSC Wed, 05/26/2010 - 05:56

Doan,

     You can look at it as happening at the same time.


One of my campaign's dialer impirt records look like this: 123|987456321|3|LName,Fname,2125551212


The dialer imports the records

it then dials and if the call connects (is answered) then

it moves the call to the ICM script (using they call type assinged, the route point)

at the same time it moves the call it transfers the BAAccountNumber value for that record being dialed.


So in your ICM script you should have a Set variable node doing something like:


set call.user.microapp.ToExtVXML[0]  = Call.BAAccountNumber

Doan Khanh Tan Thanh Thu, 05/27/2010 - 05:33

Hi,


I try with dialer to dial out and transfer to IVR. The call flow is:


1. Dialer dial out to customer phone

2. Customer answer

3. Dialer transfer to ICM script in this way: dialer call to number 1122, call manager will route to CVP. CVP now is the routing client, it send route request to ICM then ICM trigger the script

4. The call successfull. I try to send BAAccountNumber but It send null to CVP



And I try to with CTI route instead of CVP is a routing client, now CCM is a routing client. 1122 is CTI route point now. . I already create a coupe of lable for CCM PG and CVP PG like 66666666 and 888888. (I think i can get the BAAccountNumber if I use CTI route point instead of using CVP as a routing client)


1. I can make call  to 1122 by my IP phone, the call successfull I can hear some music.


But for the dialer to transfer  to  this number 1122, The call successfull route to script but it fail in Send to VRU node.


Are the any specical configuration to complete this task? Please help me!



Thanh

The easiest thing to do is for me to give you all the steps. You read through them and see how what I have aligns with what you have. Then see if you can add what you may have missed.


The config below is CVP 4.x with SIP and no Outbound Proxy. Adjust  to suit.


I may have forgotten some minor settings, but I expect you can sort that out.


Please post back here to tell the community whether you have been successful in getting this to work, and to add any comments that may help other readers. I am not writing this down merely to help one person - i.e. you.


1. Create a skill group - say CVP_Outbound


2. Create a route - CVP_Outbound.rt


3. Create a call type - say OutboundXfer_CT


4. Create a call type - say Outbound_CT


5. Assume that the transfer label on your type 10 NVRU on the UCM Routing Client is 8222222222 (not the one on the CVP.RC. Assume you have that and it's the conventional one of 8111111111)


6. Create a SIP trunk to CVP Call Server


7. Create a route pattern in UCM for 8222! associated with the SIP trunk to CVP


8. Create a Transfer to IVR Campaign - Enable IP AMD, Transfer to IVR Route Point, Terminate Tone Detect. Create the normal stuff associated with a campaign - dialer, dialer ports, query rule, import rule. Set it to power dial with 1.00 lines per "agent" (not really any agents involved here).


9. Add the Skill Group CVP_Outbound.


10. Set the number of IVR ports (less than or equal to the number of dialer ports)


11. Set the IVR Route Point - say 67008


12. Create a corresponding dialed number on the CUCM.RC.67008


13. Map the dialed number CUCM.RC.67008 to the call type OutboundXfer_CT


14. Schedule your CVP script to play back the message (or whatever it does) against the call type OutboundXfer_CT. Modify it as described in 21 below.


15. Create a route point in CUCM and assign it an extension of 67008


16. Associate this route point with your ICM JTAPI user


17. Create an admin script - say CvpOutboundAdmin


18. Set the variable CVP_Outbound.OutboundControl = PROGRESSIVE_ONLY


19. Set the variable CVP_Outbound.OutboundPercentage = 100


20. Schedule the script to run every minute. Don't forget this part.


21. Create a reservation script called, say Outbound, which has an LAA node attached to the CVP_Outbound skill group. Schedule this against the Outbound_CT. This is for phantom calls to reserve "agents".


22. Create a dialed number say 99999 on the Dialer MR PG routing client.


23. Modify your CVP script to have a "Send To VRU" node immediately after the start. If you already have one, insert a second one. You need to get the 8222222222 label to the CUCM RC, which then calls the route point and gets the call to CVP. When the script resumes, the second "Send To VRU" node returns 8111111111 and you have a static route like "8111>" to get that call leg to the voice gateway. You cannot use "Send To Originator" because the call has not been originated by the IOS gateway SIP user agent.


24. Make sure you have your customer instance defined, and associated with your NVRU, all your dialed numbers and all the NVRU scripts.


Regards,

Geoff

Doan Khanh Tan Thanh Sun, 05/30/2010 - 02:54

Thank you for your answer,


It works with me now. Some misconfiguration in calling search space in SIP trunk.


I successfull to send to BAAccountNumber to CVP when use the CCM as a routing client.


But I still thinking about how can we do the same if CVP is now run as a routing client. The dialer will transfer customer call to CVP via SIP then CVP will send route request to ICM.


Regards,


Thanh

Correct Answer

Yes, try #2. It will give you the most flexibility and you will learn how to make custom reusable action elements. The Java template to start with is as follows:


//These classes are used by custom configurable elements.
import com.audium.server.voiceElement.ActionElementBase;
import com.audium.server.voiceElement.ElementInterface;
import com.audium.server.voiceElement.Setting;
import com.audium.server.voiceElement.ElementData;
import com.audium.server.voiceElement.ElementException;

// This class is used by action elements.
import com.audium.server.session.ActionElementData;

/**
* This is the skeleton of a configurable action element. This is different
* from a standard action in that it is pre-built and the developer
* configures it in Audium Builder for Studio. The methods implemented here
* apply primarily to define the configuration for display in the Builder. Note
* that there is no need to implement the methods getExitStates because action
* elements only have a single exit state: "done".
*/
public class MyConfigurableAction extends ActionElementBase implements ElementInterface
{
    /**
     * This method is run when the action is visited. From

     * the ActionElementData object, the configuration

     * can be obtained.
     */
    public void doAction(String name, ActionElementData data)

                         throws ElementException
    {
  // PUT YOUR CODE HERE.
    }

/**
  * This method returns the name the action element will have in the Element
  * Pane in the Audium Builder for Studio.
  */
    public String getElementName()
    {
  // PUT YOUR CODE HERE.
    
        return "My action element";
    }

/**
  * This method returns the name of the folder in which this action element
  * resides. Return null if it is to appear directly under the Elements
  * folder.
  */
    public String getDisplayFolderName()
    {
  // PUT YOUR CODE HERE.
    
        return "My action element folder";
    }

/**
  * This method returns the text of a description of the action element that
  * will appear as a popup when the cursor points to the element.
  */
    public String getDescription()
    {
  // PUT YOUR CODE HERE.
    
        return "My action element description";
    }

/**
  * This method returns an array of Setting objects representing all the
  * settings this action element expects. Return null if the action element
  * does not need any settings.
  */
    public Setting[] getSettings() throws ElementException
    {
  // PUT YOUR CODE HERE.
    
        return null;
    }

/**
  * This method returns an array of ElementData objects representing the
  * element data that this action element creates. Return null if the action
  * element does not create any Element Data.
  */
    public ElementData[] getElementData() throws ElementException
    {
  // PUT YOUR CODE HERE.
 
        return null;
    }
}


Regards,

Geoff

Voiceops SSC Wed, 05/26/2010 - 04:31

Yeah, that's the one I'm using but I think I'm missing something in studio, my compiler can't seem to find the

com.audium.server.voiceElement.ActionElementBase library and therefore is not compiling the class.

I already wrote the code for the settings, and evrything else but the sincethe library is missing my actiondata references are invalid.


I'm using studio (eclipse) to build the class (java project).  In a textbook I have it says that I need to add external jars to the java project and it points to 3 jar files (framework.jar, servlet.jar, and axalan.jar)

The textbook says to get them from the vxml server, is that correct?

I only found framework.jar, servlet.jar in C:\Cisco\CallStudio\eclipse\plugins\com.audiumcorp.studio.library.framework_6.0.1, but not axalan.jar

could this be why it can't find the library?


Im using a configurable action element with 1 string input and 4 string outputs.


For the parsing part I'm going to use java split("|") to do something like this:



String BAAccountNnumber = "123|987654321|xx"


String [] output = BAAccountNnumber.split("|")


results:


output[0] = 123

output[1] = 987654321

output[2] = xx


actionData.setSessionData(resultEntityID,output[0]);
actionData.setSessionData(resultMemberID,output[1]);
actionData.setSessionData(resultTFNType,output[2]);




thanks

One word of warning.


In your case you do everything in doAction(). If you decide to call a method out of doAction(), make sure you pass variables you need to that method from doAction() - not a problem; but also make sure that the method does not try to set global variables that doAction() looks at. You can return something, of course, and doAction() can use that value. But if you need to return more than 1 item, you need to create an inner class and return an instance of that with the members filled in.


The reason is that the class executes in a multi-threaded environment with one instance of  the class created by the class loader. Globals are not allowed.


You have a simple action element, so no worries.


Regards,

Geoff

Voiceops SSC Wed, 05/26/2010 - 18:20

Thanks again for your help.  This is what I ended up doing:


This configurable action element takes a string seperated by two "|" (123|123456789|12)

and returns 3 string variables.

you can add more output variables by adding to the Setting array below.



// These classes are used by custom configurable elements.

import com.audium.server.session.ActionElementData;

import com.audium.server.voiceElement.ActionElementBase;

import com.audium.server.voiceElement.ElementData;

import com.audium.server.voiceElement.ElementException;

import com.audium.server.voiceElement.ElementInterface;

import com.audium.server.voiceElement.Setting;

import com.audium.server.xml.ActionElementConfig;


public class SOMENAMEHERE extends ActionElementBase implements ElementInterface



{


    /**


     * This method is run when the action is visited. From the ActionElementData


     * object, the configuration can be obtained.


     */


    public void doAction(String name, ActionElementData actionData) throws ElementException


    {


        try {


            // Get the configuration


            ActionElementConfig config = actionData.getActionElementConfig();




            //now retrieve each setting value using its 'real' name as defined in the getSettings method above


            //each setting is returned as a String type, but can be converted.


            String input = config.getSettingValue("input",actionData);


            String resultType = config.getSettingValue("resultType",actionData);


            String resultEntityID = config.getSettingValue("resultEntityID",actionData);


            String resultMemberID = config.getSettingValue("resultMemberID",actionData);


            String resultTFNType = config.getSettingValue("resultTFNType",actionData);




            //get the substring


            //String sub = input.substring(startPos,startPos+numChars);


            String[] BAAcctresults = input.split("\\|");




            //Now store the substring into either Element or Session data as requested


            //and store it into the variable name requested by the Studio developer


            if(resultType.equals("Element")){


                actionData.setElementData(resultEntityID,BAAcctresults[0]);


                actionData.setElementData(resultMemberID,BAAcctresults[1]);


                actionData.setElementData(resultTFNType,BAAcctresults[2]);


            } else {


                actionData.setSessionData(resultEntityID,BAAcctresults[0]);


                actionData.setSessionData(resultMemberID,BAAcctresults[1]);


                actionData.setSessionData(resultTFNType,BAAcctresults[2]);


            }


            actionData.setElementData("status","success");


        } catch (Exception e) {


            //If anything goes wrong, create Element data 'status' with the value 'failure'


            //and return an empty string into the variable requested by the caller


            e.printStackTrace();


            actionData.setElementData("status","failure");


        }




    }



    public String getElementName()


    {


        return "MEDDOC PARSER";


    }



    public String getDisplayFolderName()


    {


        return "SSC Custom";


    }



    public String getDescription()


    {


        return "This class breaks down the BAAccountNumber";


    }



    public Setting[] getSettings() throws ElementException


    {


         //You must define the number of settings here


         Setting[] settingArray = new Setting[5];




          //each setting must specify: real name, display name, description,


          //is it required?, can it only appear once?, does it allow substitution?,


          //and the type of entry allowed



        settingArray[0] = new Setting("input", "Original String",


                   "This is the string from which to grab a substring.",


                   true,   // It is required


                   true,   // It appears only once


                   true,   // It allows substitution


                   Setting.STRING);




        settingArray[1] = new Setting("resultType", "Result Type",


                "Choose where to store result \n" +


                "into Element or Session data",


                true,   // It is required


                true,   // It appears only once


                false,  // It does NOT allow substitution


                new String[]{"Element","Session"});//pull-down menu


        settingArray[1].setDefaultValue("Session");




        settingArray[2] = new Setting("resultEntityID", "EntityID",


          "Name of variable to hold the result.",


          true,   // It is required


          true,   // It appears only once


          true,   // It allows substitution


          Setting.STRING);  




        settingArray[2].setDefaultValue("EntityID");




        settingArray[3] = new Setting("resultMemberID", "MemberID",


                "Name of variable to hold the result.",


                true,   // It is required


                true,   // It appears only once


                true,   // It allows substitution


                Setting.STRING);  




        settingArray[3].setDefaultValue("MemberID");




        settingArray[4] = new Setting("resultTFNType", "TFNType",


                  "Name of variable to hold the result.",


                  true,   // It is required


                  true,   // It appears only once


                  true,   // It allows substitution


                  Setting.STRING);  




        settingArray[4].setDefaultValue("TFNType");    





return settingArray;


    }




    public ElementData[] getElementData() throws ElementException


    {


        return null;


    }


}

Actions

This Discussion