ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


SMS-Powered Applications SMS-Powered Applications

by Dejan Bosanac
06/09/2004

Every business tries to get as close as it can to its customers. The aim is to keep customers up to date with company news, products or service updates, relevant information about their accounts, or to send them notifications for important events. Email messages are widely used for these purposes and that is usually an appropriate way to inform customers. But there is a drawback to using email exclusively for communication with customers. Not all customers have a habit of checking their mailboxes regularly, so information can be delivered in a matter of hours or even days. Sometimes, this is not fast enough, and in some cases, it could be good for the company's public image to make it better. Mobile phones are a commodity today, and SMS (Short Message Service) messages offers an intriguing solution. Messages are delivered in a matter of seconds, and customers are far more likely to receive and read them, no matter their Internet habits or current locations.

This is by no means the only way SMS messages could work for you. You could provide your users with two-way communication by enabling customers to send you an SMS request in a specified format and get a response from the system. This user interface would be far from being user-friendly, but for simple request-response situations, it would work well enough.

Beside SMS messages, there are many technologies currently used for messaging by mobile users. Still, SMS messaging is the most widely adopted, and there are good reasons for that. MMS (Multimedia Messaging Service) messages have become popular, but many mobile devices doesn't support them yet, and some people are unlikely to change their current phones for several years. EMS (Enhanced Message Service) is a proprietary technology, which restricts its wide adoption among phone makers. In this light, some analysts predict that SMS will be dominant technology in this field for the next few years. So we can definitely say that it is here to stay.

There are many companies that offers SMS gateway services and enables various interfaces (HTTP, FTP, XML-RPC, SMTP) that could be used for sending (and in some cases, receiving) SMS messages. Also, you could find some gateway products and open source projects for the same purpose. This approach is appropriate for most cases, because developers know these technologies well and it doesn't require a big development effort for a solution that will work. But if you want to heavily use SMS messages or greater flexibility in your system, you will probably want to skip the "middleman" and work directly with your mobile provider.

This article will describe the basics needed for developing an SMS-powered application that can communicate directly with the mobile operator of your choice, all using Java and open source tools and libraries.

Protocol

SMS messages are transferred using Short Message Peer to Peer (SMPP) protocol. This is an open industry standard protocol developed to enable transfer of short messages between mobile users, called External Short Message Entities (ESMEs), and Short Message Service Centers (SMSCs). An ESME represents a fixed-network SMS client, and that is where our application resides in the system. For the SMSC, we will assume a mobile provider or a center that is responsible for delivering the message to mobile user. The communication may also involve Routing Entities (REs) that are responsible for message routing between ESMEs and SMSCs (and of course, other routing entities).

SMPP defines a set of operations in the form of Protocol Data Units (PDUs). PDUs are well-formed packets that are transferred between entities. A PDU consists of a header and an optional (depending on the PDU type) body. We will not dig deeper into PDU formats in this article because all of these low-level operations are abstracted by the API that will be described later in the text. Detailed description of the SMPP protocol could be found on the official SMPP forum site.

From the OSI stack perspective, SMPP presents an application layer protocol and relies on a TCP/IP (or X.25) network protocol that is responsible for packet delivery. So the basic requirement for using SMS messages is providing a reliable network connection to your mobile provider (just as it would be needed for any HTTP data transfer).

The first thing the ESME (our application) should do is to establish a session with the message center. There are three connection types that could be used, depending on the application's role in the system. An ESME can establish the connection as a transmitter (TX), meaning it can only send messages to the SMSC for a delivery to mobile users. Next, it can behave as a receiver (RX), in which case it can only receive messages from an SMSC. Finally, first two modes are combined in a transceiver (TRX) session type that enables application to both send and receive SMS messages.

In order to get a picture of how a SMPP session works, we will go through one example where the application connects to the SMSC as a transceiver, gets a request from the user (such as a request for the balance on a user's account for an online banking system), and responds back (with the user's balance). This arrangement is shown in Figure 1. This will also be our example Java application later. The labels on the arrows represents PDU types that are exchanged between an ESME and a SMSC.

Figure 1
Figure 1. Example of PDUs transferred between an ESME and a SMSC

After establishing the network connection and binding as a transceiver, the ESME is enabled to receive messages. The user sends SMS messages in an appropriate format (such as BALANCE 123456, meaning "Give me the balance for account number 123456") and the SMSC delivers the message to the application (shown as the deliver_sm and deliver_sm_resp PDUs). After checking for correct format and retrieving the data from the database, the application returns the current balance back to the user (the submit_sm and submit_sm_resp PDUs). The application can exchange messages with the message center as long as it has a valid, bound session to it. When the application wants to terminate the session, it should unbind the session (with the unbind and unbind_resp PDUs) and close the network connection.

A few more things should be said about the SMPP protocol. First, it is an asynchronous protocol, which means that the ESME could send the next message before it receives a confirmation that the previous one had been received by message center. This approach leads to better network utilization and overall performance. The asychronous nature of the protocol also affects the API architecture, which will be discussed in the later section.

Security is always a concern when one is talking about communication protocols. PDUs are transferred plain, so they are vulnerable to interception and impression attacks. If you want to prevent these kind of attacks, you should consider some additional security measures, such as using a leased line to the message center or using an SSL connection.

API

One way to make your application "speak" SMPP is to use the SMPP API, an open source Java API. We will demonstrate the basics of this library through a simple demo application.

For a start, let's define requirements for this demo application. It will serve as an extension of the e-banking system, enabling customers to check their balances using their mobile phones. For each customer there is a defined phone number that he or she must use for communicating with the system. The format of the user's request message is:

BALANCE <ACCOUNT_NUMBER>

where BALANCE is a keyword and is followed by the account number of the customer. If the user makes a proper request, he or she will receive a message containing the current balance. Otherwise, an error message with an appropriate explanation will be delivered to the phone. As a technical requirement, we will insist that the application can successfully deal with network connection problems, because for this kind of application, needing to restart every time the connection to the SMSC is lost would make the application much less usable and hard to administer.

For a start, we have to establish a connection to the SMSC and bind it with appropriate credentials. For that purpose, we should use the ie.omk.smpp.Connection class. As noted earlier, SMPP is an asynchronous protocol, but the nature of the protocol does not keep us from using it synchronously, if we desire. SMPP API enables communication of both types, with synchronous communication as the default. In our demo we will still use it in asynchronous way because of all of the benefits mentioned above. Here is the code snippet for establishing the connection to the SMSC:

private void connect() {
  try {
    conn = new Connection("localhost", 2775, true);
    conn.addObserver(this);
  } catch (UnknownHostException uhe) {
    System.exit(0);
  }

  boolean retry = false;
        
  while (!retry) {
  try {
    conn.bind(
      Connection.TRANSCEIVER, "sysId", 
      "secret", null
    );
      retry = true;
    } catch (IOException ioe) {
      try {
        sleep(60 * 1000);
      } catch (InterruptedException ie) {
      }
    }
  }
                
}

All example code for this article can be downloaded at the end of the article.

First we have to instantiate a new Connection object with the desired host and port parameters. The third parameter defines the connection as asynchronous and could be left out if you want to use some other approach.

Next, we should bind the connection with the connection type and credentials provided by the message center. We will bind as a TRANSCEIVER, as we want to both submit and receive messages. The parameters that have to be passed are system identification and a "secret," used for authentication purposes. Also, you should pass a "system type" parameter that defines the application's purpose, such as VMS for a voice mail system. This parameter is not required by all SMSCs, and its default value is null.

At this point, the underlying network connection will be established, and if the SMSC is currently down or unreachable, an IOException will be thrown. There are a few things that you could do in this case, such as log the error and exit. Our requirement is to deal with these situations, so we will try to bind again after a one minute delay.

public void run() {
  while (!exit) {
    connect();
    synchronized(this) {
      try {
        wait();
      } catch (InterruptedException ie) {
      }
    }
  }
}

Our main application thread is quite simple: it binds the connection and sleeps until the connection terminates or an exit flag is set. In the first case, we will try to bind again (see the connect method above). In the latter case, we exit the application due to some unrecoverable error such as when binding is refused from the SMSC (which means that the submitted parameters are not correct) -- in this case, there is no point executing the application until the error is resolved. This is of course only one possible architectural approach, and you should adopt this API to your needs and environment.

One more tip that could be used is to create a shutdown hook, which will unbind our application from the SMSC when the application terminates or crashes. That way, we will be sure that we have done everything to write an application that will behave properly under any circumstances. The code below simply unbinds the connection if it exists and is currently bound:

package net.nighttale.smsdemo;

import ie.omk.smpp.Connection;

public class Hook extends Thread {
        
  public void run() {
    System.out.println("Unbinding");
    Connection conn = SMSDemo.getInstance().getConnection();
    if(conn != null && conn.isBound())
      conn.force_unbind();
  }
}

Adding it to the list of runtime hooks is a simple call:

Runtime.getRuntime().addShutdownHook(new Hook());

One issue with asynchronous communication is how to implement receiving functionality. The observer pattern is the right solution for these sort of problems, so the SMPP API provides a ConnectionObserver interface for this purpose. To be enabled to receive messages (and other relevant events), our demo class has to implement this interface, and we have to write code for two methods defined by it. Next, this observer should be attached to the connection using the addObserver method, as shown in the in the connect method above. The first method we will look at is update, which accepts Connection and SMPPEvent as arguments.

public void update(Connection conn, SMPPEvent ev) {
  if (ev.getType() == SMPPEvent.RECEIVER_EXIT 
      && ((ReceiverExitEvent)ev).isException()) {
        synchronized(this) {
          notify();
        }
  }
}

There are three types of events generated by the receiver thread. One caught in the demo is SMPPEvent.RECEIVER_EXIT, which is generated when receiver thread exits either normally or due to an exception. In the latter case, the isException() method will return true, which means that the network connection to the SMSC is invalid and we should reconnect in order to continue. In this case, we wake up our main thread and try to make a new connection. The second event type is SMPPEvent.RECEIVER_EXCEPTION, which means that receiver has caught an exception that has not caused it to terminate. It would be useful to log this event, but to keep the example code simple, this logging has been left out. Finally, there is SMPPEvent.RECEIVER_START, which is generated when the thread receiver starts, and can be ignored.

The more important method of the ConnectionObserver interface is packetReceived:

public void packetReceived(Connection conn,
    SMPPPacket pack) {
  switch(pack.getCommandId()) {
    case SMPPPacket.DELIVER_SM : 
      try {
        SubmitSM response = processRequest(pack);
        SubmitSMResp smr =
            (SubmitSMResp)conn.sendRequest(response);
      } catch (Exception e) {
        e.printStackTrace();
      }
      break;
    case SMPPPacket.BIND_TRANSCEIVER_RESP :
      if (pack.getCommandStatus() != 0) {
        System.out.println("Error binding: " +
                    pack.getCommandStatus());
        exit = true;
        synchronized(this) {
          notify();
        }
      } else {
        System.out.println("Bounded");
      }
  }
}

This is the point where all the PDUs sent from the SMSC are collected in the application. In this demo, we will collect only two types: SMPPPacket.DELIVER_SM (a SMS submitted by the customer) and SMPPPacket.BIND_TRANSCEIVER_RESP (a response PDU from the SMSC to our bind request). For the binding response, if we detect that we haven't successfully bound to the SMSC, we set the exit flag, which will terminate the application.

Before we proceed to the SMS processing, it should be noted that implementing the "raw" ConnectionObserver interface leads to clumsy code, because you will have a long, ugly switch-case structure in these two methods. I used it in this demo for demonstration purposes, and I didn't handle all that has to be handled for a production application. So, if you will be coding an application that will handle (nearly) all possible cases, you should consider using the ie.omk.smpp.event.SMPPEventAdapter class, which implements this interface and has predefined methods for all packets and events that could be handled. You can learn more about this class by reading the SMPP API's Javadoc.

OK, now let's get back to our demo. All SMS messages delivered from the SMSC are handled by the processRequest method.

private SubmitSM processRequest(SMPPPacket request)
    throws BadCommandIDException {
  SubmitSM sm =
    (SubmitSM)conn.newInstance(SMPPPacket.SUBMIT_SM);
  sm.setDestination(request.getSource());
  String[] parts = request.getMessageText().split(" ");
  logPacket(request, "IN");
  if (parts[0].equalsIgnoreCase("balance")) {
    User user =
        User.findByPhone(request.getSource().getAddress());
    if (user == null)
      sm.setMessageText("Your phone number is not " +
                        "registered in our database! " +
                        "Please contact one of our offices");
    else if (!user.getAccountNumber().equalsIgnoreCase(parts[1]))
      sm.setMessageText("Account number that you " +
                        "have entered is not correct! " +
                        "Please try again");
    else
      sm.setMessageText("Balance on your account is " 
                         + user.getBalance() + "$");
  } else {
          sm.setMessageText("Wrong message format! " +
                            "Please send BALANCE " +
                            "<ACCOUNT_NUMBER>");
  }
  logPacket(sm, "OUT");
  return sm;
}

Here, we will create a response message that will be delivered to the user by calling the newInstance method of the Connection class with the appropriate PDU type (SUBMIT_SM in this case). Next, we should specify the phone number of the user by setting the destination field of the packet (setDestination), which, in the case of a simple reply, is exactly the same as the source of the received message. Now, we parse the text of the message to get all of the necessary data to process the input. Thus far, we have been discussing and coding communication, but this is the entry point to the business logic of the application. For this simple demo, I have implemented a mock-up User class to represent the business logic.

package net.nighttale.smsdemo;

public class User {

  private String phoneNumber;
  private String accountNumber;
  private float balance;
  
  public User (String phoneNumber,
               String accountNumber,
               int balance) {
    this.phoneNumber = phoneNumber;
    this.accountNumber = accountNumber;
    this.balance = balance;
  }
  
  
  public static User findByPhone(String phoneNumber) {
    if (phoneNumber.equals("063123456"))
      return new User(phoneNumber, "123456", 10000);
    else
      return null;
  }
  
  public String getAccountNumber() {
    return accountNumber;
  }
  
  public float getBalance() {
    return balance;
  }
  
  public String getPhoneNumber() {
    return phoneNumber;
  }
  
  public void setAccountNumber(String accountNumber) {
    this.accountNumber = accountNumber;
  }
  
  public void setBalance(float balance) {
    this.balance = balance;
  }
  
  public void setPhoneNumber(String phoneNumber) {
    this.phoneNumber = phoneNumber;
  }

}

The class has one finder method (findByPhone), a constructor, and getter/setter methods. For simplicity, findByPhone returns a valid user only for one phone number and null in all other cases. Of course, this should be replaced with a real class implemented in any convenient technology, such as EJBs, that would search and retrieve data from the database.

Depending on the message format, the user's telephone number, and (if the message was well-formatted) the submitted account number, some appropriate text is set as the return message to the user, using the setMessageText method. At the end, the message is submitted to the SMSC using the sendRequest method of the Connection class and passing the previously formed PDU as a parameter to it.

Simulator

When you are developing a SMPP-based application, you will probably want to set up a local environment for development and initial testing purposes. This is often useful because the process of obtaining access to an operator's SMSC could be time-consuming, and providers usually run detailed acceptance tests before letting your application access their SMSC. For all of these reasons, it is convenient to install some kind of SMSC simulator in your development environment. In this article, we will briefly describe SMPPSim, an open source Java SMSC simulator, and test our demo application with it.

After downloading and extracting the archive, you are ready to run the simulator by simply typing

startsmppsim

on a Windows platform, or

./startsmppsim

on Unix-like systems, in the root directory of the extracted simulator.

By default, the SMPPSim will open a network connection on port 2775 and wait to be bound by our application.

Configuration of SMPPSim is done through one of the props files in the conf directory. Depending on the underlying operating system, you should use the props.unix or props.win file. We will not dig deeper into setting details, and instead just use it to turn on message delivery from the SMSC to our application. To do that, you should change settings in the # DELIVERY SERVICE section. You should set the number of messages that message center will send to our application per minute with the parameter DELIVERY_MESSAGES_PER_MINUTE and the file that contains messages that will be randomly sent (DELIVER_MESSAGES_FILE). The default message delivery file is deliver_messages.csv, which can be found in the root folder of SMPPSim. Now let's make a few test cases for the demo.

063123456,1000,A test message
063123456,1000,BALANCE 123456
063123456,1000,balance 123456
063123456,1000,balance 123457
063123457,1000,balance 123456

In the delivery file, each line represents one SMS message to be delivered to the application. You should specify the user's phone number, ESME system identification, and text message, in a comma-separated fashion. Messages will be delivered to the application in random order at the rate defined in the configuration file. Recall that the findByPhone method of the User stub class will return a valid user only if the specified number is 063123456. The appropriate account number for that phone number is 123456. So this delivery file should generate all possible test cases. Set the number of messages to be delivered per minute (for example, 10), copy the delivery file (or generate one of your own) from the demo folder to SMPPSim folder, run SMPPSim, run the demo application, and watch how they communicate. You should get something like this on your console window.

2004-04-04 02:51:41,585 DEBUG [Thread-1] ie.omk.smpp.util 
   Loaded API properties from /smppapi.properties
2004-04-04 02:51:41,585 DEBUG [Thread-1] ie.omk.smpp.util 
-- listing properties --
smppapi.net.tcp.so_timeout=300000
smppapi.net.autoflush=true
smppapi.connection.rcv_daemon.ioex_count=3

Bounded
IN: 063123456 - BALANCE 123456
OUT: 063123456 - Balance on your account is 10000.0$
IN: 063123456 - balance 123457
OUT: 063123456 - Account number that you have entered is not 
   correct! Please try again
IN: 063123456 - balance 123456
OUT: 063123456 - Balance on your account is 10000.0$
IN: 063123456 - BALANCE 123456
OUT: 063123456 - Balance on your account is 10000.0$
IN: 063123456 - A test message
OUT: 063123456 - Wrong message format! Please send BALANCE 
   <ACCOUNT_NUMBER>
IN: 063123456 - balance 123456
OUT: 063123456 - Balance on your account is 10000.0$
IN: 063123457 - balance 123456
OUT: 063123457 - Your phone number is not registered in our 
   database! Please contact one of our offices
IN: 063123456 - balance 123456
OUT: 063123456 - Balance on your account is 10000.0$
IN: 063123456 - balance 123457
OUT: 063123456 - Account number that you have entered is not 
   correct! Please try again
IN: 063123457 - balance 123456
OUT: 063123457 - Your phone number is not registered in our 
   database! Please contact one of our offices
IN: 063123456 - BALANCE 123456
OUT: 063123456 - Balance on your account is 10000.0$
IN: 063123456 - balance 123457
OUT: 063123456 - Account number that you have entered is not 
   correct! Please try again

Now you can try to start and stop the simulator (to simulate network problems) and see how the application reacts.

Further Research

We've scratched the surface of the SMPP protocol and some ways you can use it on your project. There are many details that were left out, but I hope that you are at least a little curious and that you will dig deeper into this topic. To do that, you should follow one of the related links. SMS messages are definitely a powerful way to provide instant message delivery to a large audience. We mentioned just a few possible usages here, but I'm sure that more and more applications will be opened through this interface to their users.

However before starting to code your SMPP solution, check some of the existing gateway projects and products (such as Kannel). It may save you time if your requirements are not complex, and you certainly don't want to reinvent the wheel and make your own gateway if you don't need to.

Dejan Bosanac is a software developer, technology consultant and author. He is focused on the integration and interoperability of different technologies, especially the ones related to Java and the Web.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.