Interaction with the Asterisk server from a Java application

Interacting with an Asterisk server from a java application through the Asterisk Managment Interface (AMI)



If you are just starting research in this area, then the interaction with this server may seem to you somewhat confusing, as it once seemed to me.



In order not to look for the necessary bits of information on the forums in the style of answer-question, I am enclosing a small tutorial on interacting with the Asterisk server from java.



Important: I assume that once you have reached the stage of writing code, then you already have a working Asterisk server that you can access.



1) What to choose to make it convenient to work?



Definitely - Asterisk Managment Interface (AMI): this interface has a full set of functions that allow you to make a call, listen to events in real time from the server, receive a call status and interrupt it if necessary.



2) What library to connect?



This one:



<dependency> <groupId>org.asteriskjava</groupId> <artifactId>asterisk-java</artifactId> <version>2.0.4</version> </dependency>
      
      





3) What configs need to be viewed on the server?



extensions.conf - the config that describes the diaplan. You will be constantly contacting him. If in a more understandable language, then it contains scripts of what the server will do when a call comes to it to a specific number. First, a specific context is searched in the diaplan - it is written in square brackets, after which a number is searched under the tag of this context for which you are contacting.



manager.conf - config with user and password for your Asterisk server



The content of this config should be approximately as follows:



 [user_name] secret = password read = all write = all deny=0.0.0.0/0.0.0.0 permit=0.0.0.0/255.255.255.0
      
      







sip.conf - all trunks are registered here. A trunk is a phone from which we will call a client.



4) Where to start writing code?



There are two options: you either need to perform some action on the Asterisk server, or listen to events on the server. Our sequence includes both.



We describe the action plan:



  1. Open the connection to the server;
  2. We describe the scenario of work;
  3. We listen to events;
  4. Close the connection.


Accordingly, the connection is initialized during the creation of the DefaultAsteriskServer object:



 import org.asteriskjava.live.AsteriskServer; import org.asteriskjava.live.DefaultAsteriskServer;
      
      





 AsteriskServer asteriskServer = new DefaultAsteriskServer(HOSTNAME, USERNAME, PASSWORD); asteriskServer.initialize();
      
      





After opening the connection, we need to call the user. We will call this an action scenario. The description of the scenario will be in a separate class:



 /** *    */ public class ScenarioCall extends OriginateAction { private final Logger log = LoggerFactory.getLogger(ScenarioCall.class); private String TRUNK; private final String PHONE_FOR_RINGING; private final String EXTEN_FOR_APP; private final String CONTEXT_FOR_APP; public ScenarioCall(String trunk, String phoneForRinging, String extension, String context) { this.TRUNK = trunk; this.PHONE_FOR_RINGING = phoneForRinging; this.EXTEN_FOR_APP = extension; this.CONTEXT_FOR_APP = context; this.init(); } /** *         OriginateAction */ private void init() { //  String callId = ValidValues.getValidCallId(this.PHONE_FOR_RINGING); //    String channelAsterisk = ValidValues.getValidChannel(this.TRUNK, this.PHONE_FOR_RINGING); this.setContext(CONTEXT_FOR_APP); this.setExten(EXTEN_FOR_APP); this.setPriority(1); this.setAsync(true); this.setCallerId(callId); this.setChannel(channelAsterisk); log.info("Create Scenario Call: phone '{}',chanel '{}',context '{}',extension '{}'", callId, channelAsterisk, CONTEXT_FOR_APP, EXTEN_FOR_APP); } }
      
      





What do we need to understand in this scenario? First, a trunk connection is created. A trunk is the number from which you will call the subscriber. After that, a connection is created between the trunk and the subscriber, after that the connection between the subscriber and who else you need there.



It is in that order.



 this.setContext(CONTEXT_FOR_APP)
      
      



The transmitted value: the context in which we will look for the phone number with which you want to associate the subscriber (from extensions.conf).



 this.setExten(EXTEN_FOR_APP)
      
      



Transmitted value: the script that will be executed after you contacted the subscriber (from extensions.conf).



 this.setCallerId(callId)
      
      



Transmitted value: our subscriberโ€™s number



 this.setChannel(channelAsterisk)
      
      



Transmitted value: established communication channel, usually looks like this: trunk_name / phone_user.



Where to look for trunk_name? There is a sip.conf config on the asterisk server - all trunks are registered there.



Create a call:



 if (asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.CONNECTED) || asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.CONNECTING) || asteriskServer .getManagerConnection().getState().equals(ManagerConnectionState.INITIAL)) { try { ScenarioCall scenarioCall = new ScenarioCall(trank, phone, extension, context); CallBack callBackForScenarioCall = new CallBack(); asteriskServer.originateAsync(scenarioCall, callBackForScenarioCall); } catch (ManagerCommunicationException e) { //   , StateConnection    RECONNECTING,     } }
      
      





We created a call, but how to track it dynamically?



Two things are done for this: in the originateAsync method, an instance of the CallBack class is passed

and a listener is hung on the server, which will merge everything that happens to us.



A listener is needed because the CallBack class will not notify you of the end of the call when the user has already talked, nor will it notify you that the user could still transfer somewhere else.



 /** *        asteriskConnection.originateAsync  *  CallBack -     ,     *  originateAsync. CallBack      , *       ,    onNoAnswer ,  *     onBusy,     ,  onFailure,  . *        . ,      *      (      ,    ) */ public class CallBack implements OriginateCallback { /** *     PRERING,       * OriginateCallback -    */ private ChannelState resultCall = ChannelState.PRERING; /** *    ,   .         */ @Override public void onDialing(AsteriskChannel asteriskChannel) { //   ,  resultCall, //   asteriskChannel    null, //    resultCall   //   } /** *   .      *      6 - setStatus */ @Override public void onSuccess(AsteriskChannel asteriskChannel) { //   , asteriskChannel   null, // asteriskChannel.getState()      ChannelState.UP //   } /** *      ,    *      7 - setStatus () */ @Override public void onNoAnswer(AsteriskChannel asteriskChannel) { //   , //   asteriskChannel    null, //    resultCall   //   } /** *   *      7 - setStatus () */ @Override public void onBusy(AsteriskChannel asteriskChannel) { //   , //   asteriskChannel    null, //    resultCall   //   } /** *      */ @Override public void onFailure(LiveException e) { //     , //     , // onFailure      } }
      
      





How to hang a listener on an asterisk?



To do this, create an implementing class AsteriskServerListener, PropertyChangeListener.

For the created connection through the instance of the AsteriskConnection class, we implement:



  this.asteriskConnection.addAsteriskServerListener(this.callBackEventListener);
      
      





this.callBackEventListener - an instance of our listener class, born from:



 ** *    Asterisk *  PropertyChangeListener   ,     . *  AsteriskServerListener   ,     AsteriskConnection. */ public class CallBackEventListener implements AsteriskServerListener, PropertyChangeListener { public void onNewAsteriskChannel(AsteriskChannel channel) { channel.addPropertyChangeListener(this); } public void onNewMeetMeUser(MeetMeUser user) { user.addPropertyChangeListener(this); } public void onNewQueueEntry(AsteriskQueueEntry user) { user.addPropertyChangeListener(this); } public void onNewAgent(AsteriskAgent asteriskAgent) { asteriskAgent.addPropertyChangeListener(this); } /** *    .   {@link PropertyChangeEvent} *    , *        , *        CallBack * * @param propertyChangeEvent      */ public void propertyChange(PropertyChangeEvent propertyChangeEvent) { findEventEndCall(propertyChangeEvent); } private void findEventEndCall(PropertyChangeEvent event) { if (event.getSource() instanceof AsteriskChannel) { AsteriskChannel callBackChannel = (AsteriskChannel) event.getSource(); String callId = getStringWithOnlyDigits(callBackChannel.getCallerId().toString()); callId = ValidValues.getValidCallId(callId); if (callBackChannel.getState().toString().equals("HUNGUP") && event.getOldValue().toString().contains("RINGING")) { //      callBackChannel.removePropertyChangeListener(this); //     } else if (callBackChannel.getState().toString().equals("HUNGUP") && event.getOldValue().toString().contains("UP")) { //     callBackChannel.removePropertyChangeListener(this); //     } else if (callBackChannel.getState().toString().equals("HUNGUP")) { //      callBackChannel.removePropertyChangeListener(this); //     } } } private String getStringWithOnlyDigits(String strForParse) { String result = ""; if (strForParse != null && !strForParse.isEmpty()) { CharMatcher ASCII_DIGITS = CharMatcher.anyOf("<>").precomputed(); result = ASCII_DIGITS.removeFrom(strForParse.replaceAll("[^0-9?!]", "")); } return result; } }
      
      





I advise you at the very beginning just to pledge, what comes to propertyChange and look at PropertyChangeEvent, it will be a hell of a heap of everything that happens on the server. It does not filter information at all. Therefore, the conclusion: hang the listener as little as possible. Not for every call, because it can be done even in the OriginateCallback class, as far as I found. This is useless. Look at what PropertyChangeEvent objects come to you, look at the type of fields there and which ones you need. Next - welcome to the world of information processing.



A bit about data validation.



In OriginateAction.setChannel - trunk_name / phone_user is passed

phone_user - if itโ€™s Russian, it should start with a figure of eight, if the international number is with a plus.



In OriginateAction.setCallerId - the phone number of the client is transmitted,

then in CallBackEventListener it will come in callBackChannel.getCallerId ().



Will take it like this:



 String callId = getStringWithOnlyDigits(callBackChannel.getCallerId().toString());
      
      





In the end, do not forget about:



 asteriskServer.shutdown();
      
      





If you need to interrupt any call, then either in the CallBackEventListener class

to the existing communication channel we perform:



 callBackChannel.hangup();
      
      





Such a simple tutorial turned out. At first glance, of course, itโ€™s very simple, but believe me, it takes a lot of time and nerves to find information, put off all methods and leave working.



Good luck with your Asterisk servers!



Additional literature:



1) Asterisk-Java tutorial



2) Asterisk Managment Interface (AMI)



All Articles