IntroduzionePer quelli di voi che conoscono questo sito, il presente articolo non sarà una sorpresa, ma una naturale ed ovvia evoluzione di alcuni dei precedenti articoli. In particolare rappresenta un punto di convergenza per due di essi Consiglio caldamente di darci un'occhio per avere una idea precisa di tutto il disegno. Utilizzeremo codice sviluppato e spiegato nei suddetti articoli. Dalla parte Asterisk, faremo ancora uso della impagabile libreria Asterisk-Java , questa volta occupandoci della Manager API. PanoramicaCiò che vogliamo ottenere è una struttura di base di una sorta di monitor per asterisk. Il nostro programma si metterà in ascolto in attesa di nuovi eventi lanciati da asterisk, e quando una chiamata verrà ricevuta, intercetteremo il numero di telefono chiamante, interrogheremo SugarCRM per vedere se esista un Account corrispondente, e in caso affermativo otterremo alcuni dati su di esso.
Il lato Asterisk
La Asterisk-Java Manager API ci permette di interrogare Asterisk e cambiarne lo stato. Per poterla utilizzare dobbiamo abilitarla sul server Asterisk. Il come lo leggete qui . La Manager API espone eventi per i quali possiamo metterci in ascolto; in questo caso siamo interessati all'intercettazione dell'arrivo di una nuova chiamata. Gli eventi interessanti per i quali è possibile ottenere notifica in caso di vhiamata in ingresso sono: - Un nuovo "ChannelEvent" per il canale che connette il chiamante esterno con il nostro server asterisk.
- Un evento "CallerId" indicante un tentativo di identificazione del chiamante per il canale appena creato (il canale di origine)
- Un nuovo "ChannelEvent" per il canale che connette il server asterisk e la nostra "extension" (softphone, ip phone o altro)
- Un evento "Dial", che contiene gli identificativi unici dei 2 canali(originece e destinazione) più altri dati
- Un evento "CallerId" indicante un tentativo di identificazione della destinazione per il canale appena creato (il canale di destinazione)
Per semplicità ci occuperemo solo dell'evento "Dial", anche perchè espone il numero del chiamante, che è l'unico che ci interessi al momento. In una situazione di maggior complessità dovremmo anche controllare che la destinazione della chiamata siamo proprio noi e non un'altro intern. Dò per scontato cha la postazione di destinazione sia online, altrimenti l'evento "Dial" non verrebbe neanche lanciato. Ora , ecco il codice per la versione iniziale della nostra semplice classe, che ènient'altro che una semplificazione dell'esempio presente nel tutorial di Asterisk-Java: 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: La classe "implementa" "ManagerEventListener" per poter ricevere notifiche degli eventi Asterisk. Nel costruttore, per mezzo di una "factory" istanziamo una "ManagerConnection" (line 11) che utilizzeremo per connetterci al server Asterisk. "asterisk_server_url", "user" and "password" devono naturalmente essere la url del vostro server asterisk e lo user e password del manager definiti sempre su Asterisk. Il metodo "run", chiamato da "main" al lancio della classe, aggiunge la classe stessa alla liste degli oggetti "notificandi" per "managerConnection"(line 16), e alla riga 19 iniziamo un ciclo infinito che resta in attesa. La riga 21 è commentata perchè tanto non è raggiungibile, ma l'ho lasciata lì per ricordare che volendo è anche possibile fare un "logout". Il metodo "onManagerEvent" è quello che riceve gli eventi da Asterisk. Otteniamo il nome dell'evento ricevuto(riga 25) per assicuraci si tratti di un evento "Dial" e se è così per il momento ci limitiamo a stampare il "caller id" contenuto nell'evento. Non è garantito che il caller id sia il numero di telefono del chiamante(può essere non visibile, nascosto etc.), ma i lpiù delle volte lo sarà.
Il Lato SugarCRM Questo era solo un lato dell'integrazione. Ora abbiamo bisogno di passare il numero di telefono ottenuto a SugarCRM e se appartiene ad un "Account", vogliamo che ci vengano fornite informazioni sull'"Account". Per proseguire, è necessario avere confidenza con ciò che abbiamo visto in un precedente articolo: Integrare SugarCRM con applicativi Java In particolare è necessario seguire le procedure spiegate nel suddetto articolo per generare le classi che ci serviranno èer interagire con SugarCRM. Una volta compilate queste, saremo pronti per scrivere la prossima classe. Eccola: 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:}
Quasi tutto di questa classe è spiegato nel succitato articolo . A noi interessa il metodo "searchAccountByNumber". Gli passiamo il numero di telefono da cercare su SugarCRM. Dopo aver ottenuto un id di sessione SOAP da SugarCRM, utilizzando le credenziali definite nel costruttore, alle righe 29-31 interroghiamo la tabella "Accounts" di Sugar alla ricerca di un record il cui "phone_office" sia quello richiesto. Nella query chiediamo in ritorno 3 campi "name","phone_fax" and "website") e un massimo di 1 record. Cabliamo i dati eventualmente ottenuti in un Hashtable(righe 32-37) e poi facciamo un "logout" da SugarCRM. Niente di più di quanto visto nell'articolo citato. Da notare che l'"import" alla riga 3 dipende da come sono state compilate le classi per l'accesso a Sugar (sempre nel solito articolo). Ora abbiamo tutti i pezzi del puzzle e dobbiamo combinarli insieme, per cui torniamo alla nostra classe "PhoneNumberResolver". Assembliamo il puzzle Abbiamo bisogno di modificare la nostra classe "PhoneNumberResolver" in modo che quando intercetti un evento "dial", ne passi il caller id alla classe "AccountFinder" e ne riceva indietro i dati estratti da SugarCRM. Prima di tutto il codice: 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:} Vediamo le differenze dalla classe iniziale. Nel costruttore istanziamo un oggetto "AccountFinder" (riga 19), che utilizzeremo alla riga 33. Lì passeremo il caller id ricevuto dall'evento "Dial" al metodo "searchAccountByNumber" del nostro oggetto "AccountFinder", e ne otterremo un HAshtable contenente i dati dell'account SugarCRM eventualmente trovato. Poi semplicemente passiamo caller id e hashtable al nuovo metodo "showData", che comporrà una JLabel in formato html con i dati e la mostrerà in un JDialog. Noterete un ritardo tra ilmomento in cui il vostro ip/soft phone inizierà a squillare e la comparsa del JDialog. Il ritardo è dovuto al tempo di interrogazione di SugarCRM. E' possibile ottimizzare il tutto in vari modi, ad esempio facendo il "login" su sugar una sola volta e riutilizzando in seguito sempre la stessa session id (sarà necessario controllare che la sessione non sia scaduta), oppure mostrando il JDialog subito al primo squillo e poi aggiornandone i contenuti una volta ricevuti da SugarCRM. ConclusioniLe possibilità integrative tra Asterisk e SugarCRM sono innumerevoli e tutte molto interessanti. In prossimi articoli esploreremo percorsi differenti sull'argomento. Non esitate a contattarmi sedovessero interessarvi punti o utilizzi specifici al riguardo; farò del mio meglio per scriverne. Hasta la proxima.
|