JMS message conversion approaches

A number of data conversion approaches are open to JMS application designers. These approaches are not exclusive; some applications are likely to use a combination of these approaches. If your application is exchanging only text or is exchanging messages only with other JMS applications, you do not normally consider data conversion. Data conversion is performed automatically for you, by WebSphere® MQ.

You can ask a number of questions about how to approach message conversion:
Is it necessary to think about message conversion at all?
In some cases, such as JMS to JMS message transfers, and exchanging text messages with IBM® WebSphere MQ programs, IBM WebSphere MQ performs the necessary conversions for you, automatically. You might want to control data conversion for performance reasons, or you might be exchanging complex messages that have a predefined format. In cases such as these you must understand message conversion, and read the following topics.
What kinds of conversion are there?
There are four main types of conversion, which are explained in the following sections:
  1. JMS client data conversion
  2. Application data conversion
  3. Queue manager data conversion
  4. Message channel data conversion
Where should conversion be performed?
The section, Choosing an approach to message conversion: "receiver makes good", describes the usual approach of receiver makes good. Receiver makes good also applies to JMS data conversion.

JMS client data conversion

JMS client1 data conversion is the conversion of Java primitives and objects into bytes in a JMS message as it is sent to a destination, and conversion back again, when it is received. JMS client data conversion uses the methods of the JMSMessage classes. The methods are listed by JMSMessage class type in Table 1.

Conversion to and from the internal JVM representation of numbers and text is performed for read, get, set, and write methods. The conversion is performed when the message is sent, and when any of the read or get methods is called on a message that has been received.

The code page and numeric encoding used to write or set the contents of a message are defined as attributes of the destination. The destination code page and numeric encoding can be changed administratively. An application can also override the destination code page and encoding by setting the message properties that control writing or setting message content.

If you want to convert number encoding when a JMSBytesMessage message is sent to a destination that is not defined as Native encoding, you must set the message property JMS_IBM_ENCODING before sending the message. If you are following the receiver makes good pattern, or if you are exchanging messages between JMS applications, the application does not need to set JMS_IBM_ENCODING. In most cases you can leave the Encoding property as Native.

For JMSStreamMessage, JMSMapMessage, and JMSTextMessage messages, the character set identifier properties of the destination are used. Encoding is ignored on sending as numbers are written out in text format. The JMS client application program does not have to set JMS_IBM_CHARACTER_SET before sending the message if the destination character set property to apply..

To get the data in a message an application calls the JMS message read or get methods. The methods refer to the code page and encoding defined in the previous message header to create the Java primitives and objects correctly.

JMS client data conversion meets the needs of most JMS applications that are exchanging messages between one JMS client and another. You do not code any explicit data conversion. You do not use the java.nio.charset.Charset class, which is typically used when writing text to a file. The writeString and setString methods do the conversion for you.

For more details on JMS client data conversion, see JMS client message conversion and encoding.

Application data conversion

A JMS client application can perform explicit character data conversion by using the java.nio.charset.Charset class; see the examples in Figure 3 and Figure 4 . String data is converted into bytes, using the getBytes method, and sent as bytes. The bytes are converted back into text by using a String constructor that takes a byte array and a Charset. Character data is converted using the encode and decode Charset methods. Typically the message is sent or received as JMSBytesMessage, because the message part of a JMSBytesMessage does not contain anything other than the data written by the application2. You can also send and receive bytes using JMSStreamMessage, JMSMapMessage, or JMSObjectMessage.

There are no Java methods to encode and decode bytes that contain numeric data represented in different encoding formats. Numeric data is encoded and decoded automatically using the numeric JMSMessage read and write methods. The read and write methods use the value of the JMS_IBM_ENCODING attribute of the message data.

A typical use for application data conversion is if a JMS client sends or receives a formatted message from a non-JMS application. A formatted message contains text, numeric, and bytes data organized by the length of the data fields. Unless the non-JMS application has specified the message format as MQSTR   , the message is constructed as a JMSBytesMessage. To receive formatted message data in a JMSBytesMessage you must call a sequence of methods. The methods must be called in the same order the fields were written into the message. If the fields are numeric, you must know the encoding and length of the numeric data. If any of the fields contain byte or text data, you must know the length of any byte data in the message. There are two ways to convert a formatted message into a Java object that is easy to use.

  1. Construct a Java class corresponding to the record, to encapsulate reading and writing the message. Access to the data in the record is with get and set methods of the class.
  2. Construct a Java class corresponding to the record by extending the com.ibm.mq.headers class. Access to the data in the class is with type-specific accessors of the form, getStringValue(fieldname);
See Exchanging a formatted record with a non-JMS application.

Queue manager data conversion

In WebSphere MQ V7.0, code page conversion can be performed by the queue manager when a JMS client program gets a message. The conversion is the same as the conversion performed for a C program. A C program sets MQGMO_CONVERT as an MQGET GetMsgOpts parameter option; see Figure 2. A queue manager performs conversion for a JMS client program that is receiving a message, if the WMQ_RECEIVE_CONVERSION destination property is set to WMQ_RECEIVE_CONVERSION_QMGR, The JMS client program can also set the destination property; see Figure 1.

Before V7.0, conversions were always performed by the JMS client. JMS client data conversion is restricted to converting sequences of numbers and text of type and length known to the JMS client. It cannot convert data structures; see Exchanging a formatted record with a non-JMS application. In V7.0, until fix pack 7.0.1.5, if the conversion can be performed by the queue manager, it is always performed by the queue manager. From 7.0.1.5 onwards, the default conversion behavior reverts to the same as V6.0, and all conversions are performed by the JMS client. From 7.0.1.5, or 7.0.1.4 with APAR IC72897, you can set a new destination option, WMQ_RECEIVE_CONVERSION, to control where conversion is performed, and WMQ_RECEIVE_CCSID, to set the target code page; see Figure 1.

Figure 1. Enable queue manager data conversion
((MQDestination)destination).setIntProperty(
              WMQConstants.WMQ_RECEIVE_CONVERSION, 
              WMQConstants.WMQ_RECEIVE_CONVERSION_QMGR);

Or,

((MQDestination)destination).setReceiveConversion
             (WMQConstants.WMQ_RECEIVE_CONVERSION_QMGR);

The main benefit of queue manager conversion comes when exchanging messages with non-JMS applications. If the Format field in the message is defined, and the target character set, or encoding, is different to the message, the queue manager performs data conversion for the target application, if the application requests it. The queue manager converts message data formatted according to one of the predefined WebSphere MQ message types, such as a CICS® bridge header (MQCIH). If the Format field is user-defined, the queue manager looks for a data conversion exit with the name provided in the Format field.

Queue manager data conversion is used to best effect with the receiver makes good design pattern. A sending JMS client does not need to perform conversion. A non-JMS receiving program relies on the conversion exit to ensure that the message is delivered in the required code page and encoding. With a sending JMS client, and non-JMS receiver, the example applies to IBM WebSphere MQ pre- and post-V7.0. With IBM WebSphere MQ V7.0, the conversion exit can be called for a receiving JMS program as well.

You can create a data conversion exit, using the data conversion exit utility, crtmqcvx, to enable the queue manager to convert your own record formatted data. You can build your own record format, use the com.ibm.mq.headers to access it as a Java class, and use your own conversion exit to convert it. On z/OS® the utility is called CSQUCVX, and on IBM i, CVTMQMDTA. See Exchanging a formatted record with a non-JMS application.

Message channel data conversion

WebSphere MQ Sender, Server, Cluster-receiver, and Cluster-sender channels have a message conversion option, CONVERT. The contents of a message can optionally be converted when a message is sent. The conversion takes place at the sending end of the channel. The cluster-receiver definition is used to auto-define the corresponding cluster-sender channel.

Data conversion by message channels is typically used if it is not possible to use other forms of conversion.

Choosing an approach to message conversion: receiver makes good

The usual approach in WebSphere MQ application design for code conversion is receiver makes good. Receiver makes good reduces the number of message conversions. It also avoids the problem of unexpected channel errors if message conversion fails on some intermediary queue manager during message transfer. The receiver makes good rule is only broken if there is some reason why the receiver cannot make good. The receiving platform might not have the right character set, for example.

Receiver makes good is also good general guidance for JMS client applications. But in specific cases, conversion to the correct character set at source can be more efficient. Conversion from the JVM internal representation must take place when a message containing text or numeric types is sent. Conversion to the character set required by the receiver, if the receiver is not a JMS client, might remove the need for the non-JMS recipient to perform conversion. If the recipient is a JMS client, it is going to convert again, anyway, to decode the message data and create Java primitives and objects.

The difference between JMS client applications, and applications written in a language such as C, is that Java must perform data conversion. A Java application must convert numbers and text from their internal representation to an encoded format used in messages.

By setting destination, or message properties, you can set the character set and encoding used by WebSphere MQ to encode numbers and text in messages. Normally, you would leave the character set as 1208 and encoding as Native.

WebSphere MQ does not convert byte arrays. To encode strings and character arrays into byte arrays use the java.nio.charset package. Charset specifies the character set used to convert a string or character array into a byte array. You can also decode a byte array into a string or character array using a Charset. It is not good practice to rely on java.nio.charset.Charset.defaultCodePage when encoding strings and character arrays. The default Charset is typically windows-1252 on Windows, and UTF-8 on UNIX. windows-1252 is a single-byte character set and UTF-8 is a multi-byte character set.

Generally leave the destination character set and encoding properties at their default values of UTF-8 and Native when exchanging messages with other JMS applications. If you are exchanging messages containing numbers or text with a JMS application, choose one of the JMSTextMessage, JMSStreamMessage, JMSMapMessage, or JMSObjectMessage message types that fit your purpose. There are no other conversion tasks to do.

If you are exchanging messages with non-JMS applications that use a record format, it is more complicated. Unless the entire record contains text and can be transferred as a JMSTextMessage, you must encode and decode text in the application. Set the destination message type to MQ, and use JMSBytesMessage to avoid the IBM WebSphere MQ classes for JMS adding additional header and tagging information to the message data. Use JMSBytesMessage methods to write numbers and bytes, and the Charset class convert text into byte arrays explicitly. A number of factors might influence your choice of character set:
  • Performance: Can you reduce the number of conversions by transforming text into a character set that is used on the largest number of servers?
  • Uniformity: Transfer all messages in the same character set.
  • Richness: What character sets have all the code points that applications must use?
  • Simplicity: Single-byte character sets are simpler to use than variable length and multibyte character sets.

See Exchanging a formatted record with a non-JMS application. for examples of converting messages exchanged with non-JMS applications.

Examples

Table of message types and conversion types

Table 1. Message types and conversion types
  Conversion type
Message type Text Numeric Other None
JMSObjectMessage
     
getObject
setObject
JMSTextMessage
getText
setText
     
JMSBytesMessage
readUTF
writeUTF
readDouble
readFloat
readInt
readLong
readShort
readUnsignedShort
writeDouble
writeFloat
writeInt
writeLong
writeShort
readBoolean
readObject
writeBoolean
writeObject
readByte
readUnsignedByte
readBytes
readChar
writeByte
writeBytes
writeChar
JMSStreamMessage
readString
writeString
readDouble
readFloat
readInt
readLong
readShort
writeDouble
writeFloat
writeInt
writeLong
writeShort
readBoolean
readObject
writeBoolean
writeObject
readByte
readBytes
readChar
writeByte
writeBytes
writeChar
JMSMapMessage
getString
setString
getDouble
getFloat
getInt
getLong
getShort
setDouble
setFloat
setInt
setLong
setShort
getBoolean
getObject
setBoolean
setObject
getByte
getBytes
readChar
setByte
setBytes
setChar

Calling data conversion from a C program

Figure 2. Code snippet from amqsget0.c
gmo.Options = MQGMO_WAIT         /* wait for new messages            */
            | MQGMO_NO_SYNCPOINT /* no transaction                   */
            | MQGMO_CONVERT;     /* convert if necessary             */
   
     while (CompCode != MQCC_FAILED) {
     buflen = sizeof(buffer) - 1; /* buffer size available for GET   */
     memcpy(md.MsgId, MQMI_NONE, sizeof(md.MsgId));
     memcpy(md.CorrelId, MQCI_NONE, sizeof(md.CorrelId));
     md.Encoding       = MQENC_NATIVE;
     md.CodedCharSetId = MQCCSI_Q_MGR;
     
           MQGET(Hcon,          /* connection handle                 */
           Hobj,                /* object handle                     */
           &md,                 /* message descriptor                */
           &gmo,                /* get message options               */
           buflen,              /* buffer length                     */ 
           buffer,              /* message buffer                    */
           &messlen,            /* message length                    */  
           &CompCode,           /* completion code                   */
           &Reason);            /* reason code                       */

Sending and receiving text in a JMSBytesMessage

The code in Figure 3 sends a string in a BytesMessage. For simplicity, the example sends a single string, for which a JMSTextMessage is more appropriate. To receive a text string in bytes message containing a mixture of types, you must know the length of the string in bytes, called TEXT_LENGTH in Figure 4. Even for a string with a fixed number of characters, the length of the byte representation might be longer.

Figure 3. Sending a String in a JMSBytesMessage
BytesMessage bytes = session.createBytesMessage();
String codePage = CCSID.getCodepage(((MQDestination) destination)
                  .getIntProperty(WMQConstants.WMQ_CCSID));
bytes.writeBytes("In the destination code page".getBytes(codePage));
producer.send(bytes);
Figure 4. Receiving a String from a JMSBytesMessage
BytesMessage message = (BytesMessage)consumer.receive();
int TEXT_LENGTH = new Long(message.getBodyLength())).intValue();
byte[] textBytes = new byte[TEXT_LENGTH];
message.readBytes(textBytes, TEXT_LENGTH);
String codePage = message.getStringProperty(WMQConstants.JMS_IBM_CHARACTER_SET);
String textString = new String(textBytes, codePage);
1 JMS Client refers to the WebSphere MQ classes for JMS that implement the JMS interface, which runs either in client or bindings mode.
2 One exception: Data written using writeUTF starts with a 2 byte length field