Routing Messages to the MQ Dead Letter Queue

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)