Writing classes to encapsulate a record layout in a JMSBytesMessage
The purpose of this task is to explore, by example, how to combine data conversion and a fixed record layout in a JMSBytesMessage. In the task, you create some Java classes to exchange an example record structure in a JMSBytesMessage. You can modify the example to write classes to exchange other record structures.
A JMSBytesMessage is the best choice of JMS message type to exchange mixed data type records with non-JMS programs. It has no additional data inserted into the message body by the JMS provider. It is therefore the best choice of message type to use if a JMS client program interoperates with an existing IBM® MQ program. The main challenge in using a JMSBytesMessage comes with matching the encoding and character set expected by the other program. A solution is to create a class that encapsulates the record. A class that encapsulates reading and writing a JMSBytesMessage, for a specific record type, makes it easier to send and receive fixed-format records in a JMS program. By capturing the generic aspects of the interface in an abstract class, much of the solution can be reused for different record formats. Different record formats can be implemented in classes that extend the abstract generic class.
An alternative approach is to extend the com.ibm.mq.headers.Header class. The Header class has methods, such as addMQLONG, to build a record format in a more declarative way. A disadvantage of using the Header class is getting and setting attributes uses a more complicated interpretative interface. Both approaches result in much the same amount of application code.
A JMSBytesMessage can encapsulate only a single format, in addition to an MQRFH2, in one message, unless each record uses the same format, coded character set, and encoding. The format, encoding, and character set of a JMSBytesMessage are properties of all of the message following the MQRFH2. The example is written on the assumption that a JMSBytesMessage contains only one user record.
Before you begin
- Your skill level: you must be familiar with Java programming and JMS. No instructions are provided about setting up the Java development environment. It is advantageous to have written a program to exchange a JMSTextMessage, JMSStreamMessage, or JMSMapMessage. You can then see the differences in exchanging a message using a JMSBytesMessage.
- The example requires IBM WebSphere® MQ 7.0.
- The example was created using the Java perspective of the Eclipse workbench. It requires JRE 6.0 or higher. You can use the Java perspective in IBM MQ Explorer to develop and run the Java classes. Alternatively, use your own Java development environment.
- Using IBM MQ Explorer makes setting up the test environment, and debugging, simpler than using command-line utilities.
About this task
You are guided through creating two classes: RECORD and MyRecord. Together these two classes encapsulate a fixed-format record. They have methods to get and set attributes. The get method reads the record from a JMSBytesMessage and the put method writes a record to a JMSBytesMessage.
The purpose of the task is not to create a production quality class that you can reuse. You might choose to use the examples in the task to get started on your own classes. The purpose of the task is to provide you with guidance notes, primarily about using character sets, formats, and encoding, when using a JMSBytesMessage. Each step in creating the classes is explained, and aspects of using JMSBytesMessage, which are sometimes overlooked, are described.
The RECORD class is abstract and defines some common fields for a user record. The common fields are modeled on the standard IBM MQ header layout of having an eye catcher, a version, and a length field. The encoding, character set, and format fields, found in many IBM MQ headers, are omitted. Another header cannot follow a user-defined format. The MyRecord class, which extends the RECORD class, does so by literally extending the record with additional user fields. A JMSBytesMessage, created by the classes, can be processed by the queue manager data conversion exit.
scaffoldingclasses to test the RECORD and MyRecord. The extra classes are:
- TryMyRecord
- The main program to test RECORD and MyRecord.
- EndPoint
- An abstract class that encapsulates the JMS connection, destination, and session in a single class. Its interface just meets the needs of testing the RECORD and MyRecord classes. It is not an established design pattern for writing JMS applications.
Note: The Endpoint class includes this line of code after creating a destination:
((MQDestination)destination).setReceiveConversion (WMQConstants.WMQ_RECEIVE_CONVERSION_QMGR);In 7.0, from 7.0.1.5, it is necessary to turn on queue manager conversion. It is disabled by default. In 7.0, up to 7.0.1.4 queue manager conversion is enabled by default, and this line of code causes an error.
- MyProducer and MyConsumer
- Classes that extend EndPoint, and create a MessageConsumer and MessageProducer, connected and ready to accept requests.
Procedure
Results
- The results from running the TryMyRecord class:
- Sending message in coded character set 37, and using a queue manager conversion exit:
Out flags 1 text ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 Encoding 546 CCSID 37 MQ true Out flags 1 text ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 Encoding 546 CCSID 37 MQ true In flags 1 text ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 Encoding 273 CCSID UTF-8 - Sending message in coded character set 37, and not using a queue manager conversion exit:
Out flags 1 text ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 Encoding 546 CCSID 37 MQ true Out flags 1 text ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 Encoding 546 CCSID 37 MQ true In flags 1 text ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 Encoding 546 CCSID IBM037
- Sending message in coded character set 37, and using a queue manager conversion exit:
- The results from modifying the TryMyRecord class not to receive the message, and instead receiving it using the modified amqsget0.c sample. The modified sample accepts a formatted record; see Figure 2 in Exchanging a formatted record with a non-JMS application.
- Sending message in coded character set 37, and using a queue manager conversion exit:
Sample AMQSGET0 start ccsid <850>, flags <1>, message <ABCDEFGHIJKLMNOPQRSTUVWXYZ012345> no more messages Sample AMQSGET0 end - Sending message in coded character set 37, and not using a queue manager conversion exit:
Sample AMQSGET0 start MQGET ended with reason code 2110 ccsid <37>, flags <1>, message <--+-+ãÃ++ÐÊËÈiÐÎÐ+ÔÒõõμþÞÚ-±=¾¶§> no more messages Sample AMQSGET0 end
- Sending message in coded character set 37, and using a queue manager conversion exit:
To try out the example and experiment with different code pages and a data conversion exit. Create the Java classes, configure IBM MQ, and run the main program, TryMyRecord ; see Figure 1.
-
Configure IBM MQ and JMS to run the example. The instructions are for running the example on Windows.
- Create a queue manager
crtmqm -sa -u SYSTEM.DEAD.LETTER.QUEUE QM1 strmqm QM1 - Create a queue
echo DEFINE QL('Q1') REPLACE | runmqsc QM1 - Create a JNDI directory
cd c:\ md JNDI-Directory - Switch to the JMS bin directory
The JMS Administration program must be run from here. The path is
MQ_INSTALLATION_PATH\java\bin. - Create the following JMS definitions in a file called JMSQM1Q1.txt
DEF CF(QM1) PROVIDERVERSION(7) QMANAGER(QM1) DEF Q(Q1) CCSID(37) ENCODING(RRR) MSGBODY(MQ) QMANAGER(QM1) QUEUE(Q1) TARGCLIENT(MQ) VERSION(7) END - Run the JMSAdmin program to create the JMS resources
JMSAdmin < JMSQM1Q1.txt
- Create a queue manager
- You can create, alter, and browse the definitions you have created using IBM MQ Explorer.
- Run TryMyRecord.
Classes used to run example
The classes listed in figures Figure 1 to Figure 6 are also available in a compressed file; download jm25529_.zip or jm25529_.tar.gz.package com.ibm.mq.id;
public class TryMyRecord {
public static void main(String[] args) throws Exception {
MyProducer producer = new MyProducer();
MyRecord outrec = new MyRecord();
System.out.println("Out flags " + outrec.getFlags() + " text "
+ outrec.getRecordData() + " Encoding "
+ producer.getEncoding() + " CCSID " + producer.getCCSID()
+ " MQ " + producer.getMQDest());
outrec.put(producer);
System.out.println("Out flags " + outrec.getFlags() + " text "
+ outrec.getRecordData() + " Encoding "
+ producer.getEncoding() + " CCSID " + producer.getCCSID()
+ " MQ " + producer.getMQDest());
MyRecord inrec = MyRecord.get(new MyConsumer());
System.out.println("In flags " + inrec.getFlags() + " text "
+ inrec.getRecordData() + " Encoding "
+ inrec.getMessageEncoding() + " CCSID "
+ inrec.getMessageCharset());
}
}
package com.ibm.mq.id;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import com.ibm.mq.constants.MQConstants;
import com.ibm.mq.headers.MQDataException;
import com.ibm.msg.client.wmq.WMQConstants;
public abstract class RECORD implements Serializable {
private static final long serialVersionUID = -1616617232750561712L;
protected final static int UTF8 = 1208;
protected final static int MQLONG_LENGTH = 4;
protected final static int RECORD_STRUCT_ID_LENGTH = 4;
protected final static int RECORD_VERSION_1 = 1;
protected final String RECORD_STRUCT_ID = "BLNK";
protected final String RECORD_TYPE = "BLANK ";
private String structID = RECORD_STRUCT_ID;
private int version = RECORD_VERSION_1;
private int structLength = RECORD_STRUCT_ID_LENGTH + MQLONG_LENGTH * 2;
private int headerEncoding = WMQConstants.WMQ_ENCODING_NATIVE;
private String headerCharset = "UTF-8";
private String headerFormat = RECORD_TYPE;
public RECORD() {
super();
}
public RECORD(BytesMessage message) throws JMSException, IOException,
MQDataException {
super();
setHeaderCharset(message.getStringProperty(WMQConstants.JMS_IBM_CHARACTER_SET));
setHeaderEncoding(message.getIntProperty(WMQConstants.JMS_IBM_ENCODING));
byte[] structID = new byte[RECORD_STRUCT_ID_LENGTH];
message.readBytes(structID, RECORD_STRUCT_ID_LENGTH);
setStructID(new String(structID, getMessageCharset()));
setVersion(message.readInt());
setStructLength(message.readInt());
}
public String getHeaderFormat() { return headerFormat; }
public int getHeaderEncoding() { return headerEncoding; }
public String getMessageCharset() { return headerCharset; }
public int getMessageEncoding() { return headerEncoding; }
public String getStructID() { return structID; }
public int getStructLength() { return structLength; }
public int getVersion() { return version; }
protected BytesMessage put(MyProducer myProducer) throws IOException,
JMSException, UnsupportedEncodingException {
setHeaderEncoding(myProducer.getEncoding());
setHeaderCharset(myProducer.getCharset());
myProducer.setMQClient(true);
BytesMessage bytes = myProducer.session.createBytesMessage();
bytes.setStringProperty(WMQConstants.JMS_IBM_FORMAT, getHeaderFormat());
bytes.setIntProperty(WMQConstants.JMS_IBM_ENCODING, getHeaderEncoding());
bytes.setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET,
myProducer.getCCSID());
bytes.writeBytes(String.format("%1$-" + RECORD_STRUCT_ID_LENGTH + "."
+ RECORD_STRUCT_ID_LENGTH + "s", getStructID())
.getBytes(getMessageCharset()), 0, RECORD_STRUCT_ID_LENGTH);
bytes.writeInt(getVersion());
bytes.writeInt(getStructLength());
return bytes;
}
public void setHeaderCharset(String charset) {
this.headerCharset = charset; }
public void setHeaderEncoding(int encoding) {
this.headerEncoding = encoding; }
public void setHeaderFormat(String headerFormat) {
this.headerFormat = headerFormat; }
public void setStructID(String structID) {
this.structID = structID; }
public void setStructLength(int structLength) {
this.structLength = structLength; }
public void setVersion(int version) {
this.version = version; }
}
package com.ibm.mq.id;
import java.io.IOException;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import com.ibm.mq.headers.MQDataException;
public class MyRecord extends RECORD {
private static final long serialVersionUID = -370551723162299429L;
private final static int FLAGS = 1;
private final static String STRUCT_ID = "MYRD";
private final static int DATA_LENGTH = 32;
private final static String FORMAT = "MYRECORD";
private int flags = FLAGS;
private String recordData = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
public MyRecord() {
super();
super.setStructID(STRUCT_ID);
super.setHeaderFormat(FORMAT);
super.setStructLength(super.getStructLength() + MQLONG_LENGTH
+ DATA_LENGTH);
}
public MyRecord(BytesMessage message) throws JMSException, IOException,
MQDataException {
super(message);
setFlags(message.readInt());
byte[] recordData = new byte[DATA_LENGTH];
message.readBytes(recordData, DATA_LENGTH);
setRecordData(new String(recordData, super.getMessageCharset()));
}
public static MyRecord get(MyConsumer myConsumer) throws JMSException,
MQDataException, IOException {
BytesMessage message = (BytesMessage) myConsumer.receive();
return new MyRecord(message);
}
public int getFlags() { return flags; }
public String getRecordData() { return recordData; } .
public BytesMessage put(MyProducer myProducer) throws JMSException,
IOException {
BytesMessage bytes = super.put(myProducer);
bytes.writeInt(getFlags());
bytes.writeBytes(String.format("%1$-" + DATA_LENGTH + "."
+ DATA_LENGTH + "s",getRecordData())
.getBytes(super.getMessageCharset()), 0, DATA_LENGTH);
myProducer.send(bytes);
return bytes;
}
public void setFlags(int flags) {
this.flags = flags; }
public void setRecordData(String recordData) {
this.recordData = recordData; }
}
package com.ibm.mq.id;
import java.io.UnsupportedEncodingException;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import com.ibm.mq.headers.CCSID;
import com.ibm.mq.jms.MQDestination;
import com.ibm.msg.client.wmq.WMQConstants;
public abstract class EndPoint {
public Context ctx;
public ConnectionFactory cf;
public Connection connection;
public Destination destination;
public Session session;
protected EndPoint() throws NamingException, JMSException {
System.setProperty("java.naming.provider.url", "file:/C:/JNDI-Directory");
System.setProperty("java.naming.factory.initial",
"com.sun.jndi.fscontext.RefFSContextFactory");
ctx = new InitialContext();
cf = (ConnectionFactory) ctx.lookup("QM1");
connection = cf.createConnection();
destination = (Destination) ctx.lookup("Q1");
((MQDestination)destination).setReceiveConversion
(WMQConstants.WMQ_RECEIVE_CONVERSION_QMGR);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); }
protected EndPoint(String cFactory, String dest) throws NamingException,
JMSException {
System.setProperty("java.naming.provider.url", "file:/C:/JNDI-Directory");
System.setProperty("java.naming.factory.initial",
"com.sun.jndi.fscontext.RefFSContextFactory");
ctx = new InitialContext();
cf = (ConnectionFactory) ctx.lookup(cFactory);
connection = cf.createConnection();
destination = (Destination) ctx.lookup(dest);
((MQDestination)destination).setReceiveConversion
(WMQConstants.WMQ_RECEIVE_CONVERSION_QMGR);
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); }
public int getCCSID() throws JMSException {
return (((MQDestination) destination)
.getIntProperty(WMQConstants.WMQ_CCSID)); }
public String getCharset() throws UnsupportedEncodingException,
JMSException {
return CCSID.getCodepage(getCCSID()); }
public int getEncoding() throws JMSException {
return (((MQDestination) destination)
.getIntProperty(WMQConstants.WMQ_ENCODING)); }
public boolean getMQDest() throws JMSException {
if ((((MQDestination) destination).getMessageBodyStyle()
== WMQConstants.WMQ_MESSAGE_BODY_MQ)
|| ((((MQDestination) destination).getMessageBodyStyle()
== WMQConstants.WMQ_MESSAGE_BODY_UNSPECIFIED)
&& (((MQDestination) destination).getTargetClient()
== WMQConstants.WMQ_TARGET_DEST_MQ)))
return true;
else
return false; }
public void setCCSID(int ccsid) throws JMSException {
((MQDestination) destination).setIntProperty(WMQConstants.WMQ_CCSID,
ccsid); }
public void setEncoding(int encoding) throws JMSException {
((MQDestination) destination).setIntProperty(WMQConstants.WMQ_ENCODING,
encoding); }
public void setMQBody() throws JMSException {
((MQDestination) destination)
.setMessageBodyStyle(WMQConstants.WMQ_MESSAGE_BODY_UNSPECIFIED); }
public void setMQBody(boolean mqbody) throws JMSException {
if (mqbody) ((MQDestination) destination)
.setMessageBodyStyle(WMQConstants.WMQ_MESSAGE_BODY_MQ);
else ((MQDestination) destination)
.setMessageBodyStyle(WMQConstants.WMQ_MESSAGE_BODY_JMS); }
public void setMQClient(boolean mqclient) throws JMSException {
if (mqclient){
((MQDestination) destination).setTargetClient(WMQConstants.WMQ_TARGET_DEST_MQ);
if (!getMQDest()) setMQBody();
}
else
((MQDestination) destination).setTargetClient(WMQConstants.WMQ_TARGET_DEST_JMS); }
}
package com.ibm.mq.id;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageProducer;
import javax.naming.NamingException;
public class MyProducer extends EndPoint {
public MessageProducer producer;
public MyProducer() throws NamingException, JMSException {
super();
producer = session.createProducer(destination); }
public MyProducer(String cFactory, String dest) throws NamingException,
JMSException {
super(cFactory, dest);
producer = session.createProducer(destination); }
public void send(Message message) throws JMSException {
producer.send(message); }
}
package com.ibm.mq.id;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.naming.NamingException;
public class MyConsumer extends EndPoint {
public MessageConsumer consumer;
public MyConsumer() throws NamingException, JMSException {
super();
consumer = session.createConsumer(destination);
connection.start(); }
public MyConsumer(String cFactory, String dest) throws NamingException,
JMSException {
super(cFactory, dest);
consumer = session.createConsumer(destination);
connection.start(); }
public Message receive() throws JMSException {
return consumer.receive(); }
}