The Wireless Messaging API

   



This article will cover the Wireless Messaging API (WMA) in detail. Defined in the Java Community Process (JCP) by JSR 120, the WMA provides a common API for sending and receiving text and binary messages - typically of store-and-forward types, such as Short Messaging Service (SMS) messages.

The Wireless Messaging API

The WMA is an optional package based on the Generic Connection Framework (GCF) and targets the Connected Limited Device Configuration (CLDC) as its lowest common denominator, meaning that it can extend both CLDC- and CDC-based profiles. It thus supports Java 2 Platform, Mobile Edition (J2ME) applications targeted at cell phones and other devices that can send and receive wireless messages. Note that Java 2 Platform, Standard Edition (J2SE) applications will also be able to take advantage of the WMA, once JSR 197 (Generic Connection Framework Optional Package for J2SE) is complete. Figure 1 illustrates the components of the WMA.

figure 1
Figure 1: Components of the Wireless Messaging API
(Click image to enlarge.)

All the WMA components are contained in a single package, javax.wireless.messaging, which defines all the interfaces required for sending and receiving wireless messages, both binary and text. Table 1 describes the contents of this package.

Table 1: Summary of the Wireless Messaging API v1.0 ( javax.wireless.messaging)

Interface Description Methods
Message Base Message interface, from which subinterfaces (such as TextMessage and BinaryMessage) are derived getAddress(), getTimestamp(), setAddress()
BinaryMessage Subinterface of Message that provides methods to set and get the binary payload getPayloadData(), setPayloadData()
TextMessage Subinterface of Message that provides methods to set and get the text payload getPayloadText(), setPayloadText()
MessageConnection Subinterface of the GCF Connection, which provides a factory of Messages, and methods to send and receive Messages newMessage(), receive(), send(), setMessageListener(), numberOfSegments()
MessageListener Defines the listener interface to implement asynchronous notification of Message objects notifyIncomingMessage()

The sections below will survey the different components of the WMA. For the low-level details of each of the WMA methods consult the WMA specification. You can find more information on the WMA, and a reference implementation (RI) here.

Exceptions

When using the WMA you may encounter some exceptions you should handle. For example, you may get an exception if an error occurs when creating a MessageConnection, or if the application lacks sufficient permissions, or if the platform does not have sufficient resources. Table 2 described the different exceptions that WMA may throw:

Table 2: Summary of WMA Exceptions

Exception Thrown by Description
ConnectionNotFoundException Connector.open() Requested connection can't be made or the connection type does not exist.
IllegalArgumentException Connector.open() One of the arguments to open() is invalid.
open*Stream() set of methods MessageConnection doesn't support stream-based connections.
newMessage() Message type is undefined.
send() Message is incomplete or contains invalid information; for example, the message payload size exceeds the maximum length for the given messaging protocol, or the Message type is invalid.
InterruptedIOException receive() The MessageConnection closed during the received method call.
send() A timeout occurred while trying to send the message, or the Connection object was closed during the send operation
  Note: if the Connector.open() timeout parameter is set to true, the platform may throw this exception for any timeout condition encountered.
IOException Connector.open() An I/O error occurred; for example, the connection endpoint was already bound.
receive() An error occurred while receiving a message, or the method was called while the connection was closed, or the method was called on a client MessageConnection.
send() The Message couldn't be sent, or there was a network failure, or the connection was closed.
setMessageListener() The connection was closed, or an attempt was made to register a listener on a client connection.
  IOException is also thrown if any method except close() is invoked after the connection has been closed.
NullPointerException send() The Message parameter was null.
SecurityException Connector.open() The requested protocol is not permitted.
receive() The application doesn't have permission to receive message at the given port.
send() The application doesn't have permission to send a message at the given port.
setMessageListener() The application doesn't have permission to receive a message at the given port.

The "Security" section of this document has more information about security-related exceptions.

The Message Interface

The interface javax.wireless.messaging.Message is the base for all types of messages communicated using the WMA - a Message is what is sent and received, produced and consumed.

In some respects, a Message looks similar to a Datagram: it has source and destination addresses, a payload, and ways to send and block for a message. The WMA provides additional functionality, such as support for binary and text messages and a listener interface for receiving messages asynchronously.

The WMA defines two subinterfaces, BinaryMessage and TextMessage, and the specification is extensible, to allow for support of additional message types.

How messages (and related control information) are encoded for transmission is protocol-specific and transparent to the WMA. If you're interested in such low-level details, refer to the WMA specification, which lists the standard documents.

The BinaryMessage Interface

The BinaryMessage subinterface represents a message with a binary payload, and declares methods to set and get it. General methods to set and get the address of the message and get its time stamp are inherited from Message.

The TextMessage Interface

The TextMessage subinterface represents a message with a text payload, such as an SMS-based short text message. The TextMessage interface provides methods to set and get text payloads (instances of String). Before the text message is sent or received, the underlying implementation is responsible for properly encoding or decoding the String to or from the appropriate format, for example GSM 7-bit or UCS-2. General methods to set and get the address of the message and get its time stamp are inherited from Message.

The MessageConnection Interface

The MessageConnection interface is a subinterface of the Generic Connection Framework's javax.microedition.io.Connection. At the top of Figure 2 you can see the GCF, and at the bottom you can see how the MessageConnection interface relates to the rest of the WMA.

figure 2
Figure 2: The MessageConnection and Its Relationship to the GCF
(Click image to enlarge.)

As Table 1 shows, MessageConnection provides the methods newMessage(), send(), and receive(), to create, send, and receive Message objects respectively. The method numberofSegments() is of particular interest. It's used to determine segment information about the Message before it is sent - see the "Segmentation and Reassembly" section of this article for more information. This interface also defines two String constants, BINARY_MESSAGE and TEXT_MESSAGE, one of which is passed to the newMessage() factory method to create the appropriate Message object type.

As with any other GCF connection, to create a Connection (in this context a MessageConnection) call javax.microedition.io.Connector.open(), and to close it call javax.microedition.Connection.close(). You can have multiple MessageConnections open simultaneously.

A MessageConnection can be created in one of two modes: as a client connection or as a server connection. As a client it can only send messages, while server connections can both send and receive messages. You specify a connection as either client or server the same way as when making other GCF connections, by way of the URL. A URL for a client connection includes a destination address, as in:

(MessageConnection)Connector.open("sms:// +5121234567:5000");

And a URL for a server connection specifies a local address (no host, just a protocol-specific local address, typically a port number), as in:

(MessageConnection)Connector.open("sms:// :5000");

Trying to bind to an already reserved local address causes an IOException to be thrown.

The URL scheme for creating a MessageConnection is not specific to a single protocol - instead it is intended to support many wireless protocols. Which protocols are considered "wireless messaging protocols" depends on the protocol adapters included in the implementation. The WMA specification defines the following protocol adapters:

  • " sms" for Short Messaging System. SMS is bi-directional: you can create, send, and receive messages (and thus support both client and server connections).
  • " cbs" for Cell Broadcast Short Message. CBS messages are broadcast by a base station, and can only be received. Attempting to send on a cbs connection results in an IOException.

Vendors can implement additional protocol adapters and if necessary new Message subinterfaces. The WMA RI supports both SMS and CBS using transport emulation, as this article describes in the section " The WMA v1.0 Reference Implementation."

The MessageListener Interface

The MessageListener implements the Listener design pattern for receiving Message objects asynchronously; that is, without blocking while waiting for messages. This interface defines a single method; notifyIncomingMessage() is invoked by the platform each time a message is received. To register for messages, use the MessageConnection.setListener() method, as you'll see later in the article. Because some platform implementations may be single-threaded, the amount of processing within notifyIncomingMessage() should (more than should, must) be kept to a minimum. You should dispatch a separate thread to consume and process the message, so the platform spends as little time as possible in notifyIncomingMessage().

Using the WMA: Sending and Receiving Messages

The WMA is a really well designed, very simple-to-use API. Let's go over some code examples that show how to take advantage of it.

Creating a MessageConnection

First we define a helper method that returns a MessageConnection. To create a client MessageConnection just call Connector.open(), passing a URL that specifies a valid WMA messaging protocol and that includes either the local address for a server connection, or a destination address for a client connection.

/**
 *  newMessageConnection returns a new MessageConnection
 *  @param addr is the address (local or remote)
 *  @return MessageConnection that was created (client 
 *  or server)
 *  @throws Exception if an error is encountered
 */
public MessageConnection newMessageConnection(String addr)
  throws Exception {
    return((MessageConnection)Connector.open(addr));
}

Listing 1: The newMessageConnection() Helper Method

Creating and Sending a TextMessage

Then we define a helper method that creates and sends a TextMessage. Note that if the connection is a client, the destination address will already be set for you by the implementation (the address is taken from the URL that was passed when the client connection was created), but if it is a server connection, you must set the destination address. In the following helper method, a non-null URL indicates that the caller wants to set the destination address before sending. Before sending the text message, the method populates the outgoing message by calling setPayloadText().

/**
 *  sendTextMessage sends a TextMessage on the specified 
 * connection
 *  @param mc the MessageConnection
 *  @param msg the message to send
 *  @param url the destination address, typically used in
 *  server mode
 */
public void sendTextMessage(MessageConnection mc, String 
  msg, String url) {
    try {
        TextMessage tmsg =
            (TextMessage)mc.newMessage
              (MessageConnection.TEXT_MESSAGE);
        if (url!= null)
            tmsg.setAddress(url);
        tmsg.setPayloadText(msg);
        mc.send(tmsg);
    }
    catch(Exception e) {
        //  Handle the exception...
        System.out.println("sendTextMessage " + e);
    }
}

Listing 2: The sendTextMessage() Helper Method

Creating and Sending a BinaryMessage

Now let's define a helper method that creates and sends a BinaryMessage. Sending text and binary is done in very similar ways. The main difference is that the binary version expects a byte[] as the input message rather than a String and it uses setPayloadData() rather than setPayloadText() to populate the outgoing message.

/**
 *  sendBinaryMessage sends a BinaryMessage on the specified 
 *  connection
 *  @param mc the MessageConnection
 *  @param msg the message to send
 *  @param url the destination address, typically used in 
 *  server mode
 */
public void sendBinaryMessage(MessageConnection mc, byte[] 
  msg, String url) {
    try {
        BinaryMessage bmsg =
            (BinaryMessage)mc.newMessage
                (MessageConnection.BINARY_MESSAGE);
        if (url!= null)
            bmsg.setAddress(url);
        bmsg.setPayloadData(msg);
        mc.send(bmsg);
    }
    catch(Exception e) {
        //  Handle the exception...
        System.out.println("sendBinaryMessage " + e);
    }
}

Listing 3: The sendBinaryMessage() Helper Method

Waiting for a Message on a MessageConnection

Recall that only a server MessageConnection can receive messages. To wait for messages you normally dispatch a thread during initialization. This thread invokes Message.receive() to block on and consume messages. Once a message is received you must determine its type (text, binary, or other) so that you can call the appropriate get-payload method.

Let's look at the class MessageProcessor, which implements a thread of execution that waits for and processes messages. Note that MessageProcessor supports a single-pass mode, where a single message is processed, or a multi-pass mode, where it loops for messages until it is told to stop:

/**
 *  Thread of execution responsible of receiving and 
 *  processing messages
 */
class MessageProcessor implements Runnable {

    Thread th = new Thread(this);
    MessageConnection mc; // MessageConnection to handle
    boolean done; // if true, thread must exit
    boolean singlepass; // if true, read and process 1 
      message only, exit

    /**
     * Constructor for multi-pass MessageProcessor
     * @param mc is the MessageConnection for this message 
     * processor
     */
    public MessageProcessor(MessageConnection mc) {
        this.mc = mc;
        th.start();
    }

    /**
     * Constructor for single-pass MessageProcessor
     * @param mc is the MessageConnection for this message 
     * processor
     * @param singlepass if true this processor must 
     * process only one 
     *  message and exit
     */
    public MessageProcessor(MessageConnection mc, boolean 
      singlepass) {
        this.mc = mc;
        this.singlepass = singlepass;
        th.start();
    }

    /** Notify the message processor to exit */
    public void notifyDone() {
        done = true;
    }

    /** Thread's run method to wait for and process 
     *  received messages.
     */
    public void run() {
        if (singlepass == true)
            processMessage();
        else
            loopForMessages();
    }

    /** Loop for messages until done */
    public void loopForMessages() {
        while (!done)
            processMessage();
    }

    /** processMessage reads and processes a Message */
    public void processMessage() {
        Message msg = null;
        //  Try reading (maybe block for) a message
        try {
            msg = mc.receive();
        }
        catch (Exception e) {
            // Handle reading errors
            System.out.println("processMessage.receive " + e);
        }
        // Process the received message
        if (msg instanceof TextMessage) {
            TextMessage tmsg = (TextMessage)msg;
            //  Handle the text message...
            ticker.setString(tmsg.getPayloadText());
        }
        else {
            // process received message
            if (msg instanceof BinaryMessage) {
                BinaryMessage bmsg = (BinaryMessage)msg;
                byte[] data = bmsg.getPayloadData();
                //  Handle the binary message...
            }
            else {
                //  Ignore
            }
        }
    }

} // MessageProcessor

Listing 4: The MessageProcessor Class

Note how the processMessage() method tests for the appropriate message type (text or binary), invokes the appropriate method to get the payload, then does appropriate processing. In our sample it puts the message text in an LCDUI ticker.

To use the message processor class, you could dispatch a single MessageProcessor thread for all of the connection's messages, in which case the thread will block and consume messages as they are received, or you could use a message connection listener and dispatch a single-pass message processor thread for each message as it's received. The former approach will consume thread resources even when there are no messages to read and process, while the latter will use a thread only when a message comes in. Which approach you should use will depend mainly on your application requirements and resources used.

The following code snippet creates a single message processor thread for all incoming messages. This thread will block for messages:

//  Message connection
MessageConnection mc; // A server connection
    //  ...
    //  ...

/** Initial state */
public void startApp() {
    //  ...
    //  ...
    //  Create a server MessageConnection
    if (mc == null) {
        try {
            // Open the messaging inbound port.
            mc = newMessageConnection("sms://:5000");
            // Start a message processor thread, to process
            // all messages for mc.
             
                     MessageProcessor mp = new MessageProcessor(mc);
        }
        catch(Exception e) {
            System.out.println
              ("startApp.newMessageProcessor" + e);
        }
    }
    //  ...
}
                  

Listing 5: Dispatching a Single Thread to Process All Incoming Messages

Now let's use a message listener to receive notifications from the platform when a new message has arrived. Recall that a message listener is associated with a message connection. To set up a message listener you must perform a number of steps.

1) Define your application (MIDlet) as implementing the MessageListener interface. public class WMAMIDlet extends MIDlet implements MessageListener

2) Define a notifyIncomingMessage() method within your MIDlet class. Recall that you must minimize the amount of processing within this method.

/** 
 * Asynchronous callback for inbound message. 
 * @param conn the MessageConnection with incoming Message
 */
public void notifyIncomingMessage(MessageConnection conn) {
    //  Dispatch a single-pass message processor for each 
    //  incoming message.
    MessageProcessor mp = new MessageProcessor(conn, 
      _singlepass);
}

3)Register a MessageListener for the server MessageConnection, when you initialize the application in startApp(), or when necessary.

/** Initial state */
public void startApp() {
    //  ...
    //  ...
    //  Create a server MessageConnection
    if (mc == null) {
        try {
            // Open the messaging inbound port.
            mc = newMessageConnection("sms://:5000");
            // Register the listener for inbound messages.
             
                     mc.setMessageListener(this);
        }
        catch(Exception e) {
            System.out.println
              ("startApp.newMessageProcessor" + e);
        }
    }
    //  ...
}
                  

Listing 6: Using a MessageConnection Listener to Process Messages

Don't forget to de-register your message listener during cleanup, in destroyApp(), by passing null as the parameter to setMessageListener().

try {
    if (mc != null) {
         
                     mc.setMessageListener(null);
        mc.close();
    }
}
catch (IOException e) {
    //  Handle the exception...
}
                  

Listing 7: De-registering a MessageConnection Listener

In a nutshell, if we define a message listener, the platform will call notifyIncomingMessage() for each incoming message. To minimize the processing within this method we dispatched a thread to process the message.

Segmentation and Reassembly

Some messaging protocols, such as SMS, don't guarantee they'll preserve the order of exchanged messages. To preserve that order, concatenation or segmentation and reassembly is used. Segmentation and reassembly (SAR) is a feature of some low-level transports that entails breaking a large message into a number of smaller sequential, ordered segments or transmission units. For example, a message sent on the SMS network is typically limited to 160 single-byte (text) characters (or 140 binary bytes) . If a longer message is desired, then it must be segmented. Reassembly is the opposite of segmentation - it consists of putting those smaller, related segments back together into a single message.

Some transports impose a limit on the size of a single message or on the number of segments used for it. The specification mandates that WMA-based SMS implementations must support at least three SMS-protocol segments for a single message. Some implementations may support more, but applications will be more portable if they adhere to the three-segment limit, and refrain from sending messages that are larger than 456 single-byte characters, 198 double-byte characters, or 399 binary bytes. Sending a larger message may result in an IOException.

To help you deal with SAR, the WMA provides a special method called MessageConnection.numberOfSegments(), which provides segmentation information for a Message before it is sent - recall that to actually send the message you use MessageConnection.send(). Method numberOfSegments() returns the number of segments it will take to send a message (even if the connection is closed), or 0 if the message can't be sent. Let's look at a modified version of sendTextMessage() that uses numberOfSegments():

/**
 *  sendTextMessage sends a TextMessage on the specified 
 *  connection
 *  @param mc the MessageConnection
 *  @param msg the message to send
 *  @param url the destination address, typically used in 
 *  server mode
 */
public void sendTextMessage(MessageConnection mc, String 
  msg, String url) {
    try {
        TextMessage tmsg =
            (TextMessage)mc.newMessage
              (MessageConnection.TEXT_MESSAGE);
        if (url!= null)
            tmsg.setAddress(url);
        tmsg.setPayloadText(msg);
         
                     int segcount = mc.numberOfSegments(tmsg);         if (segcount == 0) {             alertUser(SEGMENTATIONERROR); // can't send,                                            // alert the user
        }
        else
            mc.send(tmsg);
    }
    catch(Exception e) {
        //  Handle the exception...
        System.out.println("sendTextMessage " + e);
    }
}
                  

Listing 8: Using the MessageConnection.numberOfSegments() Method

You can also use numberOfSegments() to warn the user about higher costs associated with sending longer messages. For example, sending a short message might cost 10 cents, while sending a large message in three segments might cost 30 cents. Users who want to control costs will welcome the opportunity to cancel longer messages.

SMSC System Property

The WMA defines the system property " wireless.messaging.sms.smsc". Its value is the Short Message Service Center (SMSC) address used for sending the messages. You can retrieve this value using the System.getProperty() method:

String smsc = System.getProperty("wireless.messaging.sms.smsc");

Security

The WMA does not define a security mechanism but instead exploits the underlying platform's security framework. On certain platforms, such as MIDP 2.0, networking operations are considered privileged operations - meaning that to open a connection or to send and receive messages, permissions must be requested by the application and granted by the platform. How permissions are requested and granted is implementation-dependent. In MIDP 2.0, permissions are requested using either the manifest defined within a signed JAR or the JAD file, and permissions are granted (or not) when the operation (in our case open(), send(), or receive()) is invoked.

The WMA defines the SecurityException class to notify the application of permission-related exceptions it encounters when invoking platform services. This exception can be thrown as follows:

  • javax.microedition.io.Connector: The WMA has added SecurityException to the Connector's connection factory method. If the application is not granted permission to create a connection for a given messaging protocol (as defined by the platform security services), SecurityException is thrown.
  • MessageConnection.send(): SecurityException is thrown if the application has no permission to send messages on the specified port.
  • MessageConnection.receive(): SecurityException is thrown if the application has no permission to receive messages on the specified port.

For more information about MIDP 2.0 permissions, see the MIDP 2.0 specification and the WMA Specification and the Recommended Practices.

The WMA v1.0 Reference Implementation

You can find a reference implementation of the 1.0 version of the Wireless Messaging API here. This section will cover how to use it with the J2ME Wireless Toolkit, version 1.0.4, to test your programs.

Configuring the WMA reference implementation

To use the WMA RI with the 1.0.4 version of the J2ME Wireless Toolkit, you must first configure the toolkit by putting the wma.jar RI file into the appropriate place and updating the wireless toolkit's internal.config file, as explained here:

Copy wma.jar

The wma.jar contains the WMA reference implementation. It supports the SMS and CBS wireless messaging protocols using datagram emulation. You can find the wma.jar file under /wma/wma1_0/lib .

Just copy the wma.jar into the $WTK_HOME/lib, where $WTK_HOME is the toolkit's base directory.

Update internal.config

Update $WTK_HOME/lib/internal.config by adding WMA-specific entries - you can just copy the following block and append it to internal.config file:

# Default values for SMS internal implementation.
com.sun.midp.io.j2me.sms.Impl: com.sun.midp.io.j2me.sms.DatagramImpl
com.sun.midp.io.j2me.sms.DatagramHost: localhost
com.sun.midp.io.j2me.sms.DatagramPortIn: 54321
com.sun.midp.io.j2me.sms.DatagramPortOut: 12345
#
# Permissions to use specific SMS features
com.sun.midp.io.j2me.sms.permission.receive: true
com.sun.midp.io.j2me.sms.permission.send: true
#
# Permissions to use specific CBS features
com.sun.midp.io.j2me.cbs.permission.receive: true
#
# Permissions to use connection handlers
javax.microedition.io.Connector.sms: true
javax.microedition.io.Connector.cbs: true
#
# Alternate SMS port for CBS emulation
com.sun.midp.io.j2me.sms.CBSPort: 24680
#
# MIDP 1.0.3 flag to enable datagram and comm connections.
com.sun.midp.io.enable_extra_protocols: true
#
# Default SMS Service Center address
wireless.messaging.sms.smsc: +17815511212
############# WMA ##################

Listing 9: WMA-specific Entries in internal.config

These entries define the configurable low-level implementation and related properties to use at runtime. In the toolkit you want to leave the default values set to datagram emulation, but make sure that DatagramPortIn and DatagramPortOut have the right values, so your client can talk to your server. For example, if you're writing a WMA test MIDlet that is both a client and a server, make sure the values are the same. If you're using the wma-tck.jar to test, as explained shortly, typically no change is required.

In datagram-emulation mode, the WMA 1.0 RI emulates the transport by packing the SMS message into a Datagram and forming the actual client or server address based on the following entries:

com.sun.midp.io.j2me.sms.DatagramHost: localhost
com.sun.midp.io.j2me.sms.DatagramPortIn: 54321
com.sun.midp.io.j2me.sms.DatagramPortOut: 12345

The reference implementation allows you to introduce simulated permission exceptions by setting the appropriate permission entry to false. Below are the permission-related entries, which by default are all set to true:

# Permissions to use specific SMS features
com.sun.midp.io.j2me.sms.permission.receive: true
com.sun.midp.io.j2me.sms.permission.send: true
#
# Permissions to use specific CBS features
com.sun.midp.io.j2me.cbs.permission.receive: true
#
# Permissions to use connection handlers
javax.microedition.io.Connector.sms: true
javax.microedition.io.Connector.cbs: true

Next is an example that disallows (by setting the entry to false) SMS receive operations and forces a SecurityException:

com.sun.midp.io.j2me.sms.permission.receive: false

If you study the RI in detail, you'll notice that support for a Comm (serial AT commands-based) low-level implementation is also included in WMA, but in version 1.0 it is non-functional.

Building Your Application

Once you have properly configured the J2ME Wireless Toolkit, you can build and test your WMA-based code.

When building and pre-verifying your application, make sure your CLASSPATH includes the WMA classes as shown in the following batch file (which uses version 1.0.4 of the J2ME Wireless Toolkit). Make sure you adjust the environment as appropriate to your setup.

@echo on
rem The Wireless Messaging API / http://www.oracle.com/technetwork/java/
cls
rem Assumes classes and tmpclasses already exists

set JAVAC_CMD=javac -g:none -bootclasspath 
set MIDP_CMD=%J2MEWTK_HOME%\bin\midp
set PREVERIFY_CMD=%J2MEWTK_HOME%\bin\preverify 
set MIDP_CLASSES=%J2MEWTK_HOME%\lib\midpapi.zip
                      set WMA_CLASSES=%J2MEWTK_HOME%\lib\wma.jar

set BASEDIR=C:\WTK104\apps\j2medeveloper
set BASESRC=%BASEDIR%\src\j2medeveloper
set TMP_CLASSES=%BASEDIR%\tmpclasses
set VERIFIED_CLASSES=%BASEDIR%\classes
set RES=%BASEDIR%\res
set LIB=%BASEDIR%\lib

set WMA_TEST_SRC=%BASESRC%\wma\*.java
set OUTPUT_JAR=j2medev.jar

%JAVAC_CMD% %MIDP_CLASSES% -classpath  
                     %WMA_CLASSES% 
  -d %TMP_CLASSES% %WMA_TEST_SRC%
%PREVERIFY_CMD% -d %VERIFIED_CLASSES% -classpath 
%MIDP_CLASSES%
                     ;%WMA_CLASSES% %TMP_CLASSES%

jar cmf MANIFEST.MF %OUTPUT_JAR% -C %VERIFIED_CLASSES% .
                  

Listing 10: Example of a build Batch File

Running Your Application

When running your application in the emulator just make sure the CLASSPATH includes the wma.jar as shown in the following batch file. Again, make sure you adjust the environment as appropriate to your setup.

@echo on
rem The Wireless Messaging API / http://www.oracle.com/technetwork/java/
cls
                      set WMA_CLASSES=%J2MEWTK_HOME%\lib\wma.jar
set MIDLET_SUITE=j2medev
set EMULATOR_CMD=%J2MEWTK_HOME%\bin\emulator
%EMULATOR_CMD% -Xdescriptor:%MIDLET_SUITE%.jad -classpath 
                      %WMA_CLASSES%;%MIDLET_SUITE%.jar
                  

Listing 11: Example of a run Batch File

Using Jakarta Ant to Build and Run Your Application

If you use Jakarta Ant, you can use the build.xml file in Listing 12 to build and run your WMA code. As with the batch files in Listings 10 and 11, build.xml assumes the directory structure of the J2ME Wireless Toolkit, version 1.0.4, and that you followed the instructions in the section "Copy wma.jar."

Put build.xml in the base directory for your application; for example, $WTK_HOME/apps/, where $WTK_HOME is the toolkit's base directory:

<project name="j2medeveloper" default="build_all" basedir=".">

    <property name="wtkbase" value="C:\WTK104"/>  
    <property name="midp_lib" value="${wtkbase}\lib\midpapi.zip"/>
    <property name="wma_lib" value="${wtkbase}\lib\wma.jar"/>

    <property name="wma_src" location="src\j2medeveloper\wma"/>

    <!-- Common -->
    
    <target name="init" description="Initialiaze">
        <mkdir dir="classes"/>
        <mkdir dir="tmpclasses"/>
        <tstamp/>
    </target>
  
    <target name="clean">
        <delete file="bin\j2medev.jar"/>        
        <delete dir="tmpclasses"/>    
        <delete dir="classes"/>
    </target>

    <!-- WMA -->
    
    <target name="compile_wma" depends="init" description="Compile WMA">
        <javac bootclasspath="${midp_lib}" srcdir="${wma_src}"
            destdir="tmpclasses" classpath="${wma_lib}"/>
    </target>
  
    <target name="preverify_wma" depends="compile_wma"
        description="Preverify WMA">
        <exec executable="${wtkbase}\bin\preverify">
            <arg line="-classpath ${midp_lib};${wma_lib}"/>
            <arg line="-d classes"/>
            <arg line="tmpclasses"/>
        </exec>
    </target>

    <target name="build_wma" depends="preverify_wma">
        <jar destfile="bin\j2medev.jar" manifest="bin\MANIFEST.MF">
            <fileset dir="classes" />
            <fileset dir="res"/>
        </jar>
    </target>

    <target name="run_wma">
        <exec executable="${wtkbase}\bin\emulator">
            <arg line="-Xdescriptor:bin\j2medev.jad"/>
            <arg line="-classpath bin\j2medev.jar;${wma_lib}"/>
        </exec>
    </target>


    <!-- Build All -->

    <target name="build_all" depends="build_wma">
    </target>

</project>

Listing 12: Ant build.xml file

Use the target build_wma to compile, preverify, and JAR your classes. Note that the creation and update of the JAD file is still a manual step. Use the target run_wma to test your code using the emulator. Use the target clean to clean out your directories and remove the old JAR file.

Sending and Receiving messages from J2SE

The WMA RI includes the JAR file wma-tck.jar, which you can use to test your WMA-based MIDlets. The primary role of the wma-tck.jar is as a plug-in to the WMA technology compatibility kit (WTK), but because it includes the basic classes needed for sending and receiving WMA messages and it is already set for execution to run a test-driver ( TCKTest.class), you can use it for testing.

Before using wma-tck.jar, make sure it''s accessible. You should be able to find it in <installed-directory>/wma/wma1_0/lib. Copy the JAR file into the $WTK_HOME/lib, where $WTK_HOME is the toolkit's base directory.

Below are the parameters for the wma-tck.jar TCKTest, followed by some examples on how to use its command-line front end to send and receive messages:

Table 2 - TCKTest Parameters

Parameter Value Description
-send sms://phonenumber: port// Target for sending messages
-text N/A Send as a text message
-binary N/A Send as a binary message
-verbose N/A Print out messages as they are sent and received
  • To create a SMS client connection and send an SMS text message:

    java -jar %J2MEWTK_HOME%\lib\wma-tck.jar -text -message "Hello GSM7" -verbose -send "sms://+5121234567:5000"
  • To create a SMS client connection and send an SMS binary message:

    java -jar %J2MEWTK_HOME%\lib\wma-tck.jar -binary -message "12345\u1234 UCS2" -verbose -send "sms://+5121234567:5000"
  • To create a SMS client connection and send an SMS text message in UCS2:

    java -jar %J2MEWTK_HOME%\lib\wma-tck.jar -text -message "12345\u1234 UCS2" -verbose -send "sms://+5121234567:5000"
  • To create a SMS server connection and wait for an SMS message:

    java -jar %J2MEWTK_HOME%\lib\wma-tck.jar -receive "sms://:5000"
  • To create a CBS connection and broadcast a CBS message:

    java -jar %J2MEWTK_HOME%\lib\wma-tck.jar -text -message "Hello GSM7" -verbose -send "cbs://+5121234567:5000"
  • To create a CBS client connection and wait for an CBS message:

    java -jar %J2MEWTK_HOME%\lib\wma-tck.jar -receive "cbs://:5000"

The wma-tck.jar uses default values for the host and port numbers - these values are already set for you to test against the wma.jar. You can change these default values via the connections.prop property file, which contains the following entries:

DatagramHost=localhost
DatagramPortIn=12345
DatagramPortOut=54321

The connections.prop property file must reside in the current directory - the directory from where you invoke the J2SE command to execute the wma-tck.jar file.

Summary

The Wireless Messaging API provides a very flexible messaging API that is protocol- and device-independent. With the WMA 1.0 reference implementation you can start writing and testing WMA-based applications today. The WMA's flexible design ensures that it can be easily extended in the future, yet already provides the most commonly used message types: text and binary. The WMA allows developers to create neat applications that incorporate messaging, such as SMS or email, as well as push behavior and consumption of short messages.




Reader Feedback
Excellent   Good   Fair   Poor  

If you have other comments or ideas for future technical tips, please type them here:

Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.


Back To Top