ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

SMS-Powered Applications
Pages: 1, 2

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.

  • SMSDemo.zip: sample code for this article
  • SMPP Forum: SMPP specification, forum, links, tools
  • SMPP API: Open source SMPP library for Java
  • SMPPSim: Open source SMSC simulator
  • Kannel: Open source WAP and SMS gateway
  • SMS Tools: SMS-related tools, including another Java API

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.