The curse of technology
The cool thing about technology demos is how everything just works, well unless you are demoing it to the boss. The equipment is usually quite new, the scope is simple enough and there are never any complications. The real world has a tendency to be more complicated.
Once a system goes live, the environment tends to be more static. The hardware which was so good on day one doesn’t get any faster as the system utilization gets higher as the system is used by more users or departments. To improve performance there may be some minor upgrades of larger disks or more memory, but even these tend offset by other software upgrades.
Depending on the size of the organization, this will continue like this until performance is unacceptable. Then either the computer will be replaced or the entire system will be upgraded with new hardware and software.
Yet in a complicated environment it can be difficult to upgrade everything at once. This might be because of lack of time or other resources to do the job but it could also be be due to incompatibilities between systems.
Vendors validate that their software works with certain versions of middleware, databases, operating systems just to name a few. It simply not feasible for them to ensure that every version of their software can work with every version of every other software package including those not yet released.
Which brings me to the problem that I encountered. The system we were dealing with was pretty old and was only validated to work with MQ 6, yet most of the rest of the organization was using MQ 7. This wasn’t a problem at the beginning when the only goal was sending simple messages from place to place.
When accounting wanted us to send some additional meta data along with our messages was when our problems began. The real problem was not because our system couldn’t support MQ 7 but because we were limited to Java 6 and the new MQ libraries were dependent on Java 7. This meant that we couldn’t use the classes IBM wrote to help out.
Old School
Back in the olden days when editors didn’t have syntax coloring and object oriented programming was an idea new developers simply wrote out streams of bytes, manipulated memory or cast void pointers to get around type checking. It gets the job done but is less understandable by those who follow.
To do this task we were stuck between a rock and a hard place, most of the organization had upgraded to MQ version 7.5 but our department was stuck with an application that could only use version 6 of Java which could not use the 7.5 libraries (which were for java 7).
The solution we had to implement was to write out the data in the same structure as IBM would have. This would allow us to write out the data into a MQ version 6 using its standard libraries and read it out and the other end with version 7 libraries.
The good news is that we don’t really need to work very hard to provide this solution.
I am not going to describe the format of the header as the source code actually does a fairly good job of that. I have commented the code showing which bytes it is actually outputting.
MQ Version 6
Putting a message with a RFH2 header
import java.io.IOException;
import com.ibm.mq.MQC;
import com.ibm.mq.MQEnvironment;
import com.ibm.mq.MQException;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
public class OldSchool {
String mqHostName;
String mqQueueManagerName;
String mqQueueChannel;
String mqQueueName;
int mqQueuePort;
String filenametoput;
String inputpath;
String userdefdata = "pink";
private final int encoding = MQC.MQENC_NATIVE;
private final int codedCharSetId = 0xfffffffe; // MQC.MQCCSI_DEFAULT;
private final static String RFH2_FORMAT = "MQHRF2 ";
private final int STRUC_LENGTH = 36;
private MQQueueManager mqQueueManager; // for QMGR object
private MQQueue queue; // for Queue object
public OldSchool() {
}
private void displayException(MQException mex, String action)
{
System.out.println("Error while " + action);
System.out.println("QMGR Name : " + mqQueueManagerName);
System.out.println("Queue Name : " + mqQueueName);
System.out.println("CC : " + mex.completionCode);
System.out.println("RC : " + mex.reasonCode);
}
public void init(String host, String managername, String channel, String queuename, int queueport)
{
mqHostName = host;
mqQueueManagerName = managername;
mqQueueChannel = channel;
mqQueueName = queuename;
mqQueuePort = queueport;
// validity checking left off.
}
public void connect()
{
try {
MQEnvironment.hostname = mqHostName;
MQEnvironment.channel = mqQueueChannel;
MQEnvironment.port = mqQueuePort;
mqQueueManager = new MQQueueManager(mqQueueManagerName);
}
catch (MQException mqExp)
{
displayException(mqExp,"doing queue manager connect");
System.exit(1);
}
}
public void disconnect()
{
try {
mqQueueManager.disconnect();
}
catch (MQException mqExp)
{
displayException(mqExp,"doing queue manager disconnect");
System.exit(1);
}
}
public void open()
{
int openOption = MQC.MQOO_OUTPUT;
try {
queue = mqQueueManager.accessQueue(mqQueueName, openOption, null, null, null);
}
catch (MQException e)
{
displayException(e,"doing queue open");
System.exit(1);
}
}
public void close()
{
try {
queue.close();
}
catch (MQException mqExp)
{
displayException(mqExp,"closing queue");
System.exit(1);
}
}
private void putMessageWithHeader(String messageTextToSend)
{
try {
// create message
MQMessage mqm = new MQMessage();
mqm.format = MQC.MQFMT_STRING;
// setup our rfh2 header, one byte a at a time
// our properties will be sent out in xml format
// oldschool
String areas = userdefdata;
while (areas.length() % 4 != 0)
{
areas = areas + " ";
}
int areasLen = areas.getBytes("UTF-8").length;
System.out.println("folder areas lengths = " + (areasLen));
int structuralLen = areasLen + STRUC_LENGTH + 4;
System.out.println("complete length = " + structuralLen);
// prepare a header
mqm.seek(0);
mqm.format = RFH2_FORMAT;
mqm.writeString("RFH "); // 52 46 48 20 - header name
mqm.writeInt(2); // 00 00 00 02 - version
mqm.writeInt(structuralLen); // 00 00 00 54 - length
mqm.writeInt(encoding); // 00 00 01 11 - encoding
mqm.writeInt(codedCharSetId); // FF FF FF FE - codedcharsetid
mqm.writeString(" "); // 20 20 20 20 20 20 20 20 - format
mqm.writeInt(0); // 00 00 00 00 - flags
// CCSID 1208 -- UTF-8
mqm.writeInt(1208); // 00 00 04 B8 - nameValueCodedCharSetId
// actually write out
// our folder stuff.
mqm.writeInt(areasLen); // 00 00 00 2C - how big is properties area (usr,jms,...)
// properties section here
mqm.write(areas.getBytes("UTF-8"));
// 3C 75 73 72 3E 3C 63 6F o
// 6C 64 73 63 68 6F 6F 6C ldschool
// 3C 2F 63 6F 64 65 62 61 ...I.ta
// 74 20 49 20 74 61 77 20 t.I.taw.
// 61 20 70 75 64 64 79 20 a.puddy.
// 74 61 74 tat
// now process our actual message
// the byte array could be contents of a file, but we
// will keep this simple.
byte[] bytearray;
bytearray = messageTextToSend.getBytes();
mqm.write(bytearray);
// then finish the job.
// send it out.
//dumpMessage(mqm);
queue.put(mqm);
System.out.println("Message sent");
}
catch (MQException mqExp)
{
displayException(mqExp,"sending message");
System.exit(1);
}
catch (IOException e)
{
System.out.println("sending message, write byte array error");
System.exit(1);
}
}
public void putHeaderMessage(String args[])
{
init(args[0],args[1],args[2],args[3],Integer.parseInt(args[4]));
connect();
open();
putMessageWithHeader("I tat I taw a puddy tat");
close();
disconnect();
}
public static void main(String[] args)
{
OldSchool myputter;
myputter = new OldSchool();
myputter.putHeaderMessage(args);
}
}
Dumping message
Sure, I could have looked around on the Internet to find a solution but as long as I can produce a message using the new libraries we can dump it to ensure we can create the same structure by hand with the old libraries.
dump message
1. 52 46 48 20 00 00 00 02 RFH.....
2. 00 00 00 54 00 00 01 11 ...T....
3. FF FF FF FE 20 20 20 20 ........
4. 20 20 20 20 00 00 00 00 ........
5. 00 00 04 B8 00 00 00 2C .......,
6. 3C 75 73 72 3E 3C 63 6F o
8. 6C 64 73 63 68 6F 6F 6C ldschool
9. 3C 2F 63 6F 64 65 62 61 ...I.ta
12. 74 20 49 20 74 61 77 20 t.I.taw.
13. 61 20 70 75 64 64 79 20 a.puddy.
14. 74 61 74 tat
It is possible that the entire solution could have been done easier. It would have been much easier if we had access to the standard MQ tools but that is a story for another time.
The key piece of code for this task was to dump out the two different messages before putting them into the queue. This is the code I used.
public void dumpMessage(MQMessage msg)
{
int mLen;
System.out.println("dump message");
try {
msg.seek(0);
mLen = msg.getTotalMessageLength();
byte binMessage[] = new byte[mLen];
msg.readFully(binMessage);
StringBuilder sb = new StringBuilder();
StringBuilder sbascii = new StringBuilder();
int lineno = 0;
int idx = 0;
for (idx = 0; idx < mLen; idx++)
{
if (idx % 8 == 0 )
{
sb.append(" " + sbascii.toString());
System.out.println(sb.toString());
sb = new StringBuilder();
sbascii = new StringBuilder();
lineno++;
if (lineno < 10) { sb.append(" " + lineno + ". "); } else { sb.append(lineno + ". "); } } sb.append(String.format("%02X ", binMessage[idx])); if (binMessage[idx] > 32 && binMessage[idx] < 127)
sbascii.append(String.format("%c", binMessage[idx]));
else
sbascii.append(".");
}
sb.append(" " + sbascii.toString() + " \n");
System.out.println(sb.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Plenty -o- reference materials
http://www.ibm.com/developerworks/websphere/library/techarticles/1001_xiao/1001_xiao.html