SugarCRM and Asterisk integration in Java – First steps

  Asterisk, Sugarcrm

SugarCRM and Asterisk integration in Java – First steps

Introduction

For those of you who know this site the present article won’t be a surprise, but a natural and obvious step following some of my previous writings. Particularly, this is a point of convergence of two of them:


I strongly advise you to have a look at them to have a better idea of the overall scene. We’ll also reuse some code developed and explained in those articles.
For the Asterisk side , we’ll use again the invaluable Asterisk-Java  library, this time focusing on the Manager API exposed by the library.

 

Overview

What we aim to achieve this time is the basic structure of a sort of asterisk monitor. Our program will be “listening” to an asterisk server for new events, and when a call is received, we’ll intercept the telephone number involved and query sugarcrm to see if a correspondent account exists, and in this case we’ll get back some of its data.

 

 

The Asterisk Side

Asterisk-Java Manager API allows us to query an Asterisk server and change its state. To be able to use it we first need to enable it on our asterisk server. You can learn how to do it here .

The Manager API exposes some events we can “listen” for, in this case we’re interested in  intercepting an incoming call.  The interesting events we are notified of by the Manager API on an incoming call are:

  • A new channel event for the channel connecting the caller and our asterisk server trunk.
  • A caller id event stating an attempt of caller id identification for the previous channel (the source channel)
  • A new channel event for the channel connecting our asterisk server to the selected extension (softphone, ip phone or whatever)
  • A dial event, which contains the unique id of the two previously created channels (the source and the destination) plus other data
  • A caller id event for the attempt of caller id identification for the last created channel (the destination channel)


For sake of simplicity, we’ll deal only with the “dial” event because it  also exposes the caller number, which is the only thing we’re interested in  now. In a more complex  situation we’ll need to check also the destination channel, to make sure our “extension” is the one involved in the call. I take for granted that the destination “extension” is online, otherwise the “dial” event won’t be fired.
Now, here is the code for the initial version of our simple class ,which is just a simpler version of the example in the Asterisk-Java tutorial :

 

   1:import java.io.IOException;
   2:import org.asteriskjava.manager.*;
   3:import org.asteriskjava.manager.event.*;
   4:
   5:public class PhoneNumberResolver implements ManagerEventListener{
   6:    private ManagerConnection managerConnection;
   7:    
   8:    public PhoneNumberResolver(){
   9:        ManagerConnectionFactory factory = new ManagerConnectionFactory(
  10:                                "asterisk_server_url", "user", "password");
  11:        this.managerConnection = factory.createManagerConnection();
  12:    }
  13:    public void run() throws IOException, AuthenticationFailedException,
  14:                        TimeoutException, InterruptedException, IllegalStateException
  15:    {
  16:        managerConnection.addEventListener(this);
  17:        managerConnection.login();
  18:        
  19:        while(true) 
  20:                Thread.sleep(60000);
  21:        // managerConnection.logoff();
  22:    }
  23:    
  24:    public void onManagerEvent(ManagerEvent event) {
  25:        String event_name = event.getClass().getSimpleName();
  26:        if(event_name.equals("DialEvent")){
  27:            DialEvent e=(DialEvent)event;
  28:            System.out.println(e.getCallerId());
  29:        }
  30:    }
  31:    
  32:    public static void main(String[] args) throws Exception
  33:    {
  34:        PhoneNumberResolver phoneNumberResolver = new PhoneNumberResolver();
  35:        phoneNumberResolver.run();
  36:    }
  37:    
  38:}
  39: 


The class implements “ManagerEventListener” to be able to be notified of asterisk events. In the constructor, by means of a “factory” we instantiate a “ManagerConnection”(line 11) that we’ll use to connect to our asterisk server.asterisk_server_url, user” and password” have obviously to be your real asterisk server url,  manager user and password defined on asterisk. The “run” method, called by “main” when the class gets launched, adds the class itself as a listener for the “managerConnection”(line 16), meaning that the current class will receive asterisk fired events. On line 17 we login to the “ManagerConnection”,
and on line 19 we enter an endless loop that just waits. Line 21 is commented out because isn’t reachable, but remember we can also logout from the connection if required.
Method  “onManagerEvent” is the one required to receive events from asterisk. We get the name of the event (line 25) to make sure it is a “Dial” event and if so we just print out the caller id. You are not guaranteed the caller id is the telephone number of the caller because of all the new available caller id services , but most of the time it is.

 

The SugarCRM Side


This was just one side of our integration. Now we need to pass the gathered telephone number to sugarcrm and if it is found to be owned by a sugarcrm “Account”, we want to be shown some “Account” information .
To go on with this part of the article, you need to feel comfortable with what we’ve explored in a previous article:

 

SugarCRM integration with custom Java applications

Particularly, you need to follow the procedure explained in that article to generate the classes we’ll use to interact with SugarCRM. Once you have them, you’ll be ready to code our next class. Here it is:

 

   1:import java.security.MessageDigest;
   2:import java.util.Hashtable;
   3:import org.beanizer.sugarcrm.*;
   4:
   5:public class AccountFinder {
   6:    private SugarsoapPortType port;
   7:    private User_auth userAuth;
   8:    
   9:    public AccountFinder(){
  10:        try{
  11:            Sugarsoap service=new SugarsoapLocator();
  12:            port=service.getsugarsoapPort(new java.net.URL("http://your_sugar_server_url/soap.php"));
  13:            userAuth=new User_auth();
  14:            userAuth.setUser_name("your_sugar_user");
  15:            MessageDigest md =MessageDigest.getInstance("MD5");
  16:            String password=getHexString(md.digest("your_sugar_password,".getBytes()));
  17:            userAuth.setPassword(password);
  18:            userAuth.setVersion("0.1");
  19:        } catch(Exception ex){
  20:            ex.printStackTrace();
  21:        }
  22:    
  23:    }
  24:    public Hashtable searchAccountByNumber(String phoneNumber){
  25:        Hashtable<String,String> hash=new Hashtable<String,String>();
  26:        try{
  27:            Set_entry_result loginRes=port.login(userAuth, "myAppName");
  28:            String sessionID = loginRes.getId();
  29:            Get_entry_list_result entryList=port.get_entry_list(sessionID,"Accounts","phone_office='" 
  30:                                                                + phoneNumber + "'", "",0,
  31:                                                                new String[]{"name","phone_fax","website"}, 1, 0);
  32:            if(entryList.getEntry_list().length>0){
  33:                Entry_value entry = (entryList.getEntry_list())[0];
  34:                Name_value[] nvl=entry.getName_value_list();
  35:                for(int i=0; i<nvl.length; i++)
  36:                    hash.put(nvl[i].getName(), nvl[i].getValue());
  37:            }
  38:            port.logout(sessionID);
  39:        } catch(Exception ex){
  40:            ex.printStackTrace();
  41:        }
  42:        return hash;
  43:    }
  44:    private String getHexString(byte[] b) throws Exception {
  45:        StringBuffer hex = new StringBuffer();
  46:        for (int i=0;i<b.length;i++) {
  47:                hex.append(Integer.toHexString(0xFF & b[i]));
  48:        }
  49:        return hex.toString();
  50:    }        
  51:}

Almost everything about this class is explained in the aforesaid article . What is interesting now is the “searchAccountByNumber” method. We pass it a phone number to look for in SugarCRM. After obtaining a SOAP session id from SugarCRM, using the credential defined in the constructor, on line 29-31 we query the “Accounts” table of sugar for a record whose “phone_office” field matches  the phone number requested. In the query we ask for 3 fields to be returned(“name”,”phone_fax” and “website”), and a maximum of one record. We then build a Hashtable with the eventually returned values (line 32-37) and then logout from our soap session. Nothing more than what already seen in the cited article. Note that the “import” statement on line 3 depends on how you built the SugarCRM soap classes (again, see the relative article).
Now we have all the pieces of our puzzle and need to merge them together. So come back to our “PhoneNumberResolver” class.

 

Assembling the puzzle

What we need to do know is to modify our “PhoneNumberResolver” class so that when a “dial” event gets intercepted, we can pass the callerId to the “AccountFinder” class and get back the data extracted from SugarCRM.  The code first:

 

   1:import java.io.IOException;
   2:import java.util.Date;
   3:import java.util.Enumeration;
   4:import java.util.Hashtable;
   5:import javax.swing.JFrame;
   6:import javax.swing.JLabel;
   7:import javax.swing.JOptionPane;
   8:import org.asteriskjava.manager.*;
   9:import org.asteriskjava.manager.event.*;
  10:
  11:public class PhoneNumberResolver implements ManagerEventListener{
  12:    private ManagerConnection managerConnection;
  13:    private AccountFinder af;
  14:    
  15:    public PhoneNumberResolver(){
  16:        ManagerConnectionFactory factory = new ManagerConnectionFactory(
  17:                                "asterisk_server_url", "user", "password");
  18:        this.managerConnection = factory.createManagerConnection();
  19:        af=new AccountFinder();
  20:    }
  21:    public void run() throws IOException, AuthenticationFailedException,TimeoutException,
                                                                       InterruptedException, IllegalStateException
  22:    {
  23:        managerConnection.addEventListener(this);
  24:        managerConnection.login();
  25:        while(true)
  26:            Thread.sleep(60000);
  27:    }
  28:    public void onManagerEvent(ManagerEvent event) {
  29:        String event_name = event.getClass().getSimpleName();
  30:        if(event_name.equals("DialEvent")){
  31:            DialEvent e=(DialEvent)event;
  32:            System.out.println(e.getCallerId());
  33:            Hashtable hash=af.searchAccountByNumber(e.getCallerId());
  34:            this.showData(e.getCallerId(),hash);
  35:        }
  36:    }
  37:    private void showData(String callerId,Hashtable hash){
  38:        String message="<html><font color=green>Time</font> " +  (new Date()).toString() + "<br>";
  39:        if(hash.isEmpty()){
  40:            message += "<font color=red>Phone number not found</font>";
  41:        } else{
  42:            message +="<font color=green>Caller number</font> " + callerId + "<br>";
  43:            for(Enumeration keys=hash.keys(); keys.hasMoreElements();){
  44:                String key=keys.nextElement().toString();
  45:                String value=hash.get(key).toString();
  46:                message += "<font color=green>" + key + ":</font> " + value + "<br>";
  47:
  48:            }
  49:        
  50:        }
  51:        message +="</html>";
  52:        final String finalMessage=message;
  53:        Runnable run=new Runnable(){
  54:            public void run(){
  55:                JOptionPane.showMessageDialog((new JFrame()),
  56:                            new JLabel(finalMessage), 
  57:                            "New incoming call",JOptionPane.INFORMATION_MESSAGE);                
  58:            }
  59:        };
  60:        (new Thread(run)).start();
  61:    }
  62:    public static void main(String[] args) throws Exception
  63:    {
  64:        PhoneNumberResolver phoneNumberResolver = new PhoneNumberResolver();
  65:        phoneNumberResolver.run();
  66:    }
  67:}


Let’s see what’s different from the initial version. In the constructor we instantiate an “AccountFinder” object (line 19), that we use on line 33. There we pass the caller id received from the “Dial” event to the method “searchAccountByNumber” of our “AccountFinder” object, and we get back an Hashtable containing data about the eventually found account on SugarCRM. Then we simply pass the caller id and the hashtable to the new method “showData”, which just composes an html JLabel with the data and shows it in a JDialog.
You will note a delay between the time your ip phone starts ringing and the JDialog gets shown. The delay is due to the time involved in querying SugarCRM. You can optimize this in several ways, i.e. logging in to SugarCRM SOAP once and reuse the valid session id over and over(in this case you need to take care of session expiration time), or showing a dialog when the “dial” event is received and then just updating it when you get back the information from SugarCRM.

 

Conclusions

Integration capabilities between Asterisk and SugarCRM are virtually endless and really interesting. In future articles we’ll explore new paths on this subject. Don’t hesitate to ask me if you are interested in specific topics regarding integration between the two platforms and I’ll do my best to write articles about them.

Hasta la proxima.

LEAVE A COMMENT