I recently had a fun problem to solve for a client of mine, essentially they wanted to route messages to the MQ Series dead letter queue and have the MQ dead letter queue handler move those messages to the queue they defined in the dead letter.


 


The first problem was getting the message on the MQ dead letter queue, John and Anil gave some great pointers here, it turns out to send a message to the dead letter queue the message needs to be pre-pended with the dead letter header (DLH) and the MQMD_Format property needs to be set to MQ “MQDEAD  ” so that MQ knows to expect the DLH.


 


Serializing the Dead Letter Header


To achieve this I wrote a DLH utilities component that allows the various fields of the DLH header to be set, the component also serializes the DLH into a byte[] so that it may be pre-pended to the message. The component was called from a custom send pipeline component which allows the key fields to be set at design time, the pipeline component also sets the MQMD_Format property to the message context.


 


The format of the MQ DLH struct is as follows:


 


char[] strucId = new char[4]; // Structure identifier – MQCHAR4 StrucId


int version = 1; // Structure version number


int reason; // Reason message arrived on dead-letter


char[] destQName = new char[48];// Name of original destination queue


char[] destQMgrName  = new char[48];// Name of orig dest queue manager


int encoding; // Numeric encoding of data that follows MQDLH


int codedCharSetId; // Char set identifier of data that follows MQDLH


char[] format = new char[8]; // Format name of data that follows MQDLH


int putApplType; // Type of app that put message on DLH


char[] putApplName = new char[28]; // Name of app that put msg on DLH


char[] putDate = new char[8]; // Date when message was put the DLH


char[] putTime = new char[8]; // Time when message was put the DLH


 


This struct needs to be serialised into a byte[] and pre-pended to the message:


 


public byte[] SerializeHeader()


{


      byte[] header = new byte[172];


      int index = 0;


      byte[] tmp = null;


 


      // strucId


      int written = System.Text.Encoding.UTF8.GetBytes(strucId, 0, strucId.Length, header, index);


      index += 4;


                       


      // version


      tmp = BitConverter.GetBytes(version);


      tmp.CopyTo(header, index);


      index += 4;                  


 


      // reason


      tmp = BitConverter.GetBytes(reason);


      tmp.CopyTo(header, index);


      index += 4;


 


      // destQName


      written = System.Text.Encoding.UTF8.GetBytes(destQName, 0, destQName.Length, header, index);


      index += 48;


 


      // destQMgrName


      written = System.Text.Encoding.UTF8.GetBytes(destQMgrName, 0, destQMgrName.Length, header, index);


      index += 48;


 


      // encoding


      tmp = BitConverter.GetBytes(encoding);


      tmp.CopyTo(header, index);


      index += 4;


 


      // codedCharSetId


      tmp = BitConverter.GetBytes(codedCharSetId);


      tmp.CopyTo(header, index);


      index += 4;


 


      // format


      written = System.Text.Encoding.UTF8.GetBytes(format, 0, format.Length, header, index);


      index += 8;


 


      // putApplType


      tmp = BitConverter.GetBytes(putApplType);


      tmp.CopyTo(header, index);


      index += 4;


 


      // putApplName


      written = System.Text.Encoding.UTF8.GetBytes(putApplName, 0, putApplName.Length, header, index);


      index += 28;


 


      // putDate – Format: yyyymmdd


      written = System.Text.Encoding.UTF8.GetBytes(putDate, 0, putDate.Length, header, index);


      index += 8;


 


      // putTime – Format: hhmmss00


      written = System.Text.Encoding.UTF8.GetBytes(putTime, 0, putTime.Length, header, index);


      index += 8;


 


      return header;


}


 


Pipeline Component


The MQ adapter does not directly support setting this property, so the custom send pipeline component is responsible for pre-pending the message data stream with the DLH and setting the message context property which the MQ adapter will then set on the MQ message:


 


private static PropertyBase mqmd_Format = new MQSeries.MQMD_Format();


       


private const string MQFMT_DEAD_LETTER_HEADER = “MQDEAD  ;


 


// Set MQMD_Format property to MQFMT_DEAD_LETTER_HEADER – to indicate the message


// is prepended with the dead letter header…


inmsg.Context.Write(    mqmd_Format.Name.Name,


mqmd_Format.Name.Namespace,


MQFMT_DEAD_LETTER_HEADER );


 


// Pre-pend the message body with the MQS dead letter header…


inmsg.BodyPart.Data = DeadLetterHelper.BuildDeadLetterMessage(


inmsg.BodyPart.GetOriginalDataStream(),


_DestinationQueue,


_QueueManager,


_ApplicationName );


 


All that is required then is for the send port to be configured to send the message to the SYSTEM.DEAD.LETTER.QUEUE.


 


Dead Letter Handler


Once the message is on the dead letter queue the dead handler (runmqdlq.exe) can be configured to take the message off the queue and put it on the destination queue defined in the DLH. This proved to be a little tricky to get working, thanks to Jason for his help in getting the dead letter handler working. 


 


runmqdlq.exe SYSTEM.DEAD.LETTER.QUEUE QM_demoappserver < qrule.rul


 


The handler may be fed a rule (qrule.rul), the example here removes the DLH and puts the message on the queue specified in the DLH, one thing to watch out for is the rule below needs to have a CRLF at the end!!:


 


INPUTQM(QM_demoappserver) INPUTQ(‘SYSTEM.DEAD.LETTER.QUEUE’) WAIT(NO)


ACTION(FWD) FWDQ(&DESTQ) HEADER(NO)