How to Monitor and Control Cisco Phones with JTAPI

When creating our Cloud Call Center platform, Zomnio, we wanted agents to be able to leverage their existing Cisco investments by logging in to their Cisco phones, just like Cisco UCCX or UCCE. For this to work we need to be able to monitor the status of the agent’s phone and forward call control requests from the web-based agent desktop. Luckily, Cisco’s call processing and phone control software, Unified Communications Manager (UCM), provides a powerful API for monitoring and controlling phones: JTAPI. In this post, we’ll give a brief introduction to JTAPI and how to use it in your own applications.

What is JTAPI

JTAPI stands for Java Telephony API. It is a protocol for communicating with phone systems. Computer programs can use this API to place phone calls, answer calls, receive phone events, and more. Cisco Unified Call Manager, or CUCM, exposes call control of phones and other telephony devices via JTAPI. This enables custom applications to monitor and control Cisco phones.

At Cloverhound, we’ve used JTAPI to enable agents to connect to their Cisco Phones via their Zomnio Agent Desktop. More on that later.

The API Basics

JTAPI allows for both first-party call control and third-party call control. In a first-party call control scenario, a user has direct control over a single phone. A piece of software running directly on an end-user’s PC using JTAPI to control their phone would fall into this category. In third-party, a user has control over multiple phones in a phone system. This mode is useful for server-side applications, such as a web app that monitors and controls phones on a user’s behalf. Because JTAPI was designed to cover both scenarios, it models the phone system and phone calls using the more general third-party view, even when it is being used for first-party call control. This is important to note because a third-party view does not distinguish between the local end and the remote end of a call; the two ends are symmetrical.

Here are some of the basic JTAPI concepts, copied over from DZone:

JTapiPeer

The JtapiPeer interface represents a vendor’s particular implementation of the Java Telephony API. JtapiPeer is the first object an application must instantiate.

Provider

A provider represents the telephony software-entity that interfaces with a telephony subsystem. The rest of the JTAPI objects are derived from the provider.

Address

An Address object represents what we commonly think of as a “telephone number.”

Terminal

A Terminal represents a physical hardware endpoint connected to the telephony domain. Address and terminal objects exist in a many-to-many relationship. In other words, a phone can have multiple numbers and a number can be shared among multiple phones.

Call

A Call object represents a telephone call. A two-party call has two Connections, and a conference call has three or more Connections. Each Connection models the relationship between a Call and an Address. Address objects represent the logical endpoints of a telephone call. Terminal objects represent the physical endpoints of a telephone call.

Connection

A Connection represents a link (i.e. an association) between a Call object and an Address object. Connections objects are containers for zero or more TerminalConnection objects. Connection objects represent the relationship between the Call and the Address, whereas TerminalConnection objects represent the relationship between the Connection and the Terminal.

Using Cisco’s JTAPI Implementation

  1. Create an End User in  UCM and add that user to the “Standard CTI Allow Control of All Devices” permissions group.
  2. Download the JTAPI jar file from the UCM administration page. This can be found under ‘plugins’ in the menu.
  3. Develop a Java Application and include the JTAPI jar file in the build path.
  4. Connect the Java Application to UCM using the following code:
    String providerString = providerName + ";login=" + cucmLogin + ";passwd=" + cucmPasswd;
    JtapiPeer peer = JtapiPeerFactory.getJtapiPeer ( null );
    Provider provider = peer.getProvider(providerString);

    where providerName is the url of our CUCM, and the credentials are of the End User we created in step 1.

  5. Deploy your Java Application to a server that is in the same network (or with connectivity to) your CUCM.
  6. Voilà

Example Code

Let’s walk through an example use case. I’ve created a simple Java application that listens to events of a Cisco phone. If the phone begins ringing, the application will then answer the call. You can check out the full code on github.

Here’s the main class of the application:

final public class JtapiCiscoExample {
  
    public static void main(String[] args) {
    
        int curArg = 0;
        String cucmUrl = args[curArg++];
        String cucmLogin = args[curArg++];
        String cucmPasswd = args[curArg++];
        String extension = args[curArg++];
    
        String providerString = cucmUrl + ";login=" + cucmLogin + ";passwd=" + cucmPasswd;
        
        try {
            JtapiPeer peer = JtapiPeerFactory.getJtapiPeer ( null );
            Provider provider = peer.getProvider(providerString);  
            addCallObserver(provider, extension);
        } catch( Exception e ) {
            System.out.println("Failed to connect to cucm");
        }
    }    
 
 
    static void addCallObserver(Provider provider, String extension) throws Exception {
    
        CiscoAddress address = (CiscoAddress)provider.getAddress(extension);
        address.addCallObserver((CallEv[] events) -> {
                (new PhoneListener(address.getName())).callChangedEvent(events);
        });
    }
  
}

The application can be started via the command line, and expects the user to pass in the url of your Call Manager, the credentials of the end user you created with proper JTAPI permissions, and the extension of the device you would like to monitor. The application connects to your Call Manager using your Call Manager url and credentials. It then adds a call observer to your extension. The callChangedEvent method of our PhoneListener class gets called whenever an event is received. It is important to note that this code will not compile unless you have added your JTAPI jar file to the build path.

Let’s look at the PhoneListener class:

final class PhoneListener implements CallObserver {
  
    private String extension;
    public PhoneListener(String extension) {
        this.extension = extension;
    }

    @Override
    public void callChangedEvent(CallEv[] events) {
        for (CallEv callEvent : events){
            try {
          handleCallEvent(callEvent);
      } catch (Exception e) {   
          e.printStackTrace();
      }
        }
    }
    
    
    private void handleCallEvent(CallEv callEvent) throws Exception { 
        
        String[] eventDetails = callEvent.toString().split(" ");
        if(eventDetails.length < 2) {
            System.out.println("Current event has no extension info..ignoring");
            return;
        }
        
        String eventName = eventDetails[0].toString();
        String eventExtn = eventDetails[1].split(":")[0];
        
        if (eventName.equals("ConnAlertingEv") && eventExtn.equals(extension) ){
            System.out.println("Phone is ringing...answering");
            
            CallImpl call = (CallImpl)callEvent.getCall();
            getTerminalConnection(call).answer();
        }
        
        
    }
    
    
    private CallControlTerminalConnection getTerminalConnection(CallImpl call) {
        System.out.println("Getting CallControlTerminalConnection");
        
        Connection[] connections = call.getConnections();
        for(Connection connection : connections) {
            if(connection.getAddress().getName().equals(extension)) {
                return (CallControlTerminalConnection)(connection.getTerminalConnections()[0]);
            }
        }
        
        System.out.println("No terminal connection found");
        return null;
    }
    
}

The callChangedEvent method calls handleCallEvent on each event. The handleCallEvent method extracts the event name and extension from the callEv object. If the event is the ringing event, and if it’s for the extension we’re monitoring, we answer the call. Why do we need to worry about extension if we’re only monitoring one device? Because if we use our phone to dial another device, we will receive the event for the other device, because our phone made the call. To answer the call, we obtain the call object from the event. We then retrieve the terminal connection associated with that call and our extension. We finally call the answer method on the terminal connection.

That’s all for now! Thanks for reading.

Additional Resources:

https://dzone.com/articles/jtapi-overview
https://developer.cisco.com/site/jtapi/overview/technical.gsp
https://downloads.avaya.com/elmodocs2/AES/3.1.1/jtapi/javax/telephony/callcontrol/CallControlCall.html
http://asterisk-jtapi.sourceforge.net/jtapi.html