using System;
using System.IO;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Messaging;
using System.Web.Services.Protocols;
namespace WSAltTransports
{
/// <summary>WSAltTransports.MQRequestCreator</summary>
/// <author>SGregory</author>
/// <date>02 January 2003</date>
/// <remarks>Creation Factory for MQ-style WebRequests</remarks>
public sealed class MQRequestCreator : IWebRequestCreate
{
/// <summary>WSAltTransports.MQRequestCreator.Create</summary>
/// <author>SGregory</author>
/// <date>02 January 2003</date>
/// <remarks>Creates an MQWebRequest object</remarks>
/// <param name="Uri" type="System.Uri">URI indicating endpoint</param>
/// <returns type="System.Net.WebRequest">MQWebRequest object</returns>
public WebRequest Create(Uri Uri)
{
// Create a new request
return new MQWebRequest(
Uri,
m_ResponseUri);
}
/// <summary>WSAltTransports.MQRequestCreator.ResponseUri</summary>
/// <value>Response Url for the request</value>
public Uri ResponseUri
{
get
{
return m_ResponseUri;
}
set
{
m_ResponseUri = value;
}
}
// Private member data
private Uri m_ResponseUri = null;
}
/// <summary>WSAltTransports.MQWebRequest</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>This class handles the specific implementation of the MQ transport</remarks>
public sealed class MQWebRequest : MyBaseWebRequest
{
/// <summary>WSAltTransports.MQWebRequest</summary>
/// <author>SGregory</author>
/// <date>02 January 2003</date>
/// <remarks>This is the entry point for all "MQ://" requests</remarks>
public MQWebRequest(
Uri Url,
Uri ResponseUrl)
{
// Check the scheme for the Uri is as expected
if(Url.Scheme.ToLower() != "mq") // This class is only for mq urls
throw new NotSupportedException("This protocol [" + Url.Scheme + "] is not supported");
// Set data members
m_RequestUri = Url;
m_szMethod = string.Empty; // No default for this class
this.ResponseUri = ResponseUrl;
}
// Force MQ to derive a default response Queue name from the supplied request name
// Since the Uri handler seems to interpret something like "mq://freddy" as "mq://freddy/"
// need to figure out the proper name
protected override void SetResponseUriFromRequestUri(Uri requestUri)
{
string strAbsReqUri = requestUri.AbsoluteUri;
if (strAbsReqUri.EndsWith("/") == true)
this.ResponseUri = new Uri(strAbsReqUri.Substring(0, requestUri.AbsoluteUri.Length-1) + "_resp");
else
this.ResponseUri = new Uri(strAbsReqUri + "_resp");
}
/// <summary>WSAltTransports.MQWebRequest.GetResponse</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>
/// Does the work of placing the serialised Soap message on the named Queue
/// and waiting for a response on the Response Queue.
/// NOTES:
/// 1) The URI for the MQ Queue names look like:
/// mq://freddy
/// 2) The default Response Queue to wait on is "<queuename>_resp"
/// e.g. if an outbound Queue is called "freddy" the response Queue will
/// be named "freddy_resp" by default unless it is changed by calling the
/// public property <code>ResponseUri</code> on this class.
/// 3) Note for some reason the Uri handler converts all names to lower case
/// and MQ seems to be case-sensitive when it comes to queue names, so
/// the queues need to be set up with some care.
/// </remarks>
/// <returns type="System.Net.WebResponse">Our own special brand of WebResponse</returns>
public override WebResponse GetResponse()
{
MQWebResponse objMQWebResponse = null;
string strQueueManagerName = string.Empty;
string strQueueName = string.Empty;
string strQueueManagerNameResp = string.Empty;
string strQueueNameResp = string.Empty;
MQAX200.MQSession objMQSession = null;
MQAX200.MQQueueManager objQueueManager = null;
MQAX200.MQQueue objQueue = null;
MQAX200.MQQueue objQueueResponse = null;
MQAX200.MQMessage objMsg = null;
MQAX200.MQMessage objMsgResp = null;
MQAX200.MQPutMessageOptions objPutMsgOpts = null;
MQAX200.MQGetMessageOptions objGetMsgOpts = null;
try
{
// Figure out the return URI, or generate one on the fly
if (this.ResponseUri == null)
SetResponseUriFromRequestUri(m_RequestUri);
// Access the Soap serialised stream as an array of bytes
byte[] bytBody = new Byte[m_RequestStream.Length];
m_RequestStream.Read(bytBody, 0, bytBody.Length);
// Create an MQ message. It requires:
//
// * a QManager to be linked to (default)
// * a Q Name alias
// * a message body
//
// Select the QueueManager (could be local or remote) and queue
// What is the Queue Manager and Queue Name we need to direct the message at?
ResolveManagerAndQueueName(
this.m_RequestUri,
out strQueueManagerName,
out strQueueName);
// What is the Queue Manager and Queue Name we need to look for a response message at?
ResolveManagerAndQueueName(
this.m_ResponseUri,
out strQueueManagerNameResp,
out strQueueNameResp);
// Open the MQ Queue with the given Name / Alias
objMQSession = new MQAX200.MQSessionClass();
objQueueManager = GetQManager(objMQSession, strQueueManagerName, true);
objQueue = GetQ(objQueueManager, strQueueName, true);
// Set up the message to send
objMsg = (MQAX200.MQMessage)objMQSession.AccessMessage();
// Set up a format type of string - this allows AS/400 targets to do implicit EBCDIC conversion
// MQ on Windows targets do not reacte to this format setting.
objMsg.Format = "MQSTR ";
objPutMsgOpts = (MQAX200.MQPutMessageOptions)objMQSession.AccessPutMessageOptions();
// Set up a little speciality that may help our other end...
objMsg.ReplyToQueueManagerName = strQueueManagerNameResp;
objMsg.ReplyToQueueName = strQueueNameResp;
// Set up the contents of the output message
// We need to send a byte array to cover off DIME etc....
objMsg.Write(bytBody);
try
{
// Finally, put the message to the queue
objQueue.Put(objMsg, objPutMsgOpts);
}
catch(Exception e)
{
// Throw the exception
throw new WebException(
"Failed to send the message to the MQ request queue.",
e,
WebExceptionStatus.SendFailure,
null);
}
finally
{
// Close the Queue
objQueue.Close();
// And close Soap Stream....
m_RequestStream.InternalClose();
}
// Return the message ID that we will use as the Correlation ID
// when coming to read a response message
string strCorrelationID = objMsg.MessageId;
// Try the read with wait
string strMsgRecv = string.Empty;
// Before we do this, is the recieve on a queue that is owned by the same queue manager?
if (strQueueManagerNameResp != strQueueManagerName)
{
// No - so tidy old queue manager
objQueueManager.Commit();
objQueueManager.Disconnect();
// ...and sort the new one
objQueueManager = GetQManager(objMQSession, strQueueManagerNameResp, false);
}
try
{
// Access the response queue from the chosen Manager
objQueueResponse = GetQ(objQueueManager, strQueueNameResp, false);
objMsgResp = (MQAX200.MQMessage)objMQSession.AccessMessage();
objMsgResp.CorrelationId = strCorrelationID;
objGetMsgOpts = (MQAX200.MQGetMessageOptions)objMQSession.AccessGetMessageOptions();
// Could choose to use Syncpoint here, or not bother
objGetMsgOpts.Options = (int)MQAX200.MQ.MQGMO_NO_SYNCPOINT + (int)MQAX200.MQ.MQGMO_WAIT;
objGetMsgOpts.WaitInterval = this.Timeout;
objQueueResponse.Get(objMsgResp, objGetMsgOpts, System.Reflection.Missing.Value);
// Use the appropriate reader
strMsgRecv = objMsgResp.MessageData.ToString();
// This fudge is required since some naughty (Java) server side implementations
// don't strip information like JMS header info from the start of the response
// message.
int idxStart = strMsgRecv.IndexOf("<?xml version");
if (idxStart > 0)
strMsgRecv = strMsgRecv.Substring(idxStart);
}
catch(Exception e)
{
// This handler handles exceptions from the receive process that are non-retryable
// These include timeouts waiting on the message, and all other exceptions.
if (e.Message.IndexOf("ReasonCode = 2033, ReasonName = MQRC_NO_MSG_AVAILABLE") >= 0)
{
// Throw the exception as a Timeout
throw new WebException(
"Timeout waiting on MQ resource.",
e,
WebExceptionStatus.Timeout,
null);
}
else
// Have we already processed this exception in some way?
if ((e is System.Net.WebException) == false)
{
// No - Just throw it up the way as a general Receive failure
throw new WebException(
"Failed to receive the message from the MQ response queue.",
e,
WebExceptionStatus.ReceiveFailure,
null);
}
else
{
// It's prepared already from a lower level, so just
// throw it on up the chain
throw e;
}
}
finally
{
// Close queue
if (objQueueResponse != null)
objQueueResponse.Close();
}
// If we get here all is well.
// Convert the result into a byte array
byte[] buf = new System.Text.UTF8Encoding().GetBytes(strMsgRecv);
MemoryStream stResponse = new MemoryStream();
stResponse.Write(buf, 0, buf.Length);
// Create a return, and stream results into it...
objMQWebResponse = new MQWebResponse();
objMQWebResponse.SetDownloadStream(stResponse);
}
catch(Exception e)
{
// All exeptions caught here that are not of the WebException variety
// get cast as ProtocolErrors - these are exceptions that we do not have
// a mapping to a standard WebException status type.
// Tidy
if (objQueueManager != null)
{
objQueueManager.Commit();
objQueueManager.Disconnect();
}
// Close serialised Soap Stream....if it needs closing
m_RequestStream.InternalClose();
// Have we already processed this exception in some way?
if ((e is System.Net.WebException) == false)
{
// No - Create a MQWebResponse - this is a protocol specific error
objMQWebResponse = new MQWebResponse(
(int)QueueTransportErrors.UnexpectedFailure,
e.Message,
e.StackTrace);
// And throw the exception
throw new WebException(
"MQ Pluggable Protocol failure.",
e,
WebExceptionStatus.ProtocolError,
objMQWebResponse);
}
else
{
// It's prepared already from a lower level, so just
// throw it on up the chain
throw e;
}
}
// If we get here we are in business, so return a repsonse
return objMQWebResponse;
}
/// <summary>WSAltTransports.MQWebRequest.ResolveManagerAndQueueName</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>
/// Selects the Queue Manager and Queue Name from the passed Uri.
/// NOTE - MQ appears to be case sensitive in the way it handles
/// Queue Managers and Queue Names. This is a shame as it seems that
/// the Uri specification converts all to a lower case notation.
/// </remarks>
private void ResolveManagerAndQueueName(
Uri vUri,
out string ostrQueueManager,
out string ostrQueueName)
{
// Initialise outputs
ostrQueueManager = string.Empty;
ostrQueueName = string.Empty;
// Was a Queue Manager even supplied?
if (vUri.LocalPath == "/")
{
// No - so the host is simply the name of the Queue
ostrQueueName = vUri.Host;
}
else
{
// Yes - so split to a Queue manager and Queue
ostrQueueManager = vUri.Host;
ostrQueueName = vUri.LocalPath.Substring(1);
}
}
/// <summary>WSAltTransports.MQWebRequest.GetQManager</summary>
/// <author>Simon G</author>
/// <date>24 February 2003</date>
/// <remarks>
/// Given an MQ Session instance and a QueueManager name,
/// gets an object representing the Queue Manager if it exists
/// </remarks>
/// <param name="vobjMQSession" type="MQAX200.MQSession">Session reference</param>
/// <param name="vstrQueueManager" type="string">Queue Manager name</param>
/// <param name="vblnIsRequest" type="System.bool">true if this is a request</param>
/// <returns type="MQAX200.MQQueueManager">Queue Manager instance</returns>
/// <preconditions>Valid Session Manager object</preconditions>
/// <postconditions>Queue Manager instance returned or exception thrown</postconditions>
private MQAX200.MQQueueManager GetQManager(
MQAX200.MQSession vobjMQSession,
string vstrQueueManager,
bool vblnIsRequest)
{
MQAX200.MQQueueManager objQueueManager = null;
try
{
// Get a Queue Manager instance first
objQueueManager = (MQAX200.MQQueueManager)vobjMQSession.AccessQueueManager(vstrQueueManager);
}
catch(Exception e)
{
string strTypeOfQueue = (vblnIsRequest == true) ? "request" : "response";
string strTypeOfOperation = (vblnIsRequest == true) ? "send request" : "read response";
// Failed to get Queue manager - report this. It could be for one of several
// reasons:
//
// * The Queue Manager cannot be found (rememeber - case sentitive!)
// * General connect failure
if (e.Message.IndexOf("ReasonCode = 2058, ReasonName = MQRC_Q_MGR_NAME_ERROR") >= 0)
{
// Throw a NameResolution failure
throw new WebException(
"Failed to locate queue manager '" + vstrQueueManager + "' when trying to " + strTypeOfOperation + ".",
e,
WebExceptionStatus.NameResolutionFailure,
null);
}
else
{
// Throw a geenral connection failure
throw new WebException(
"Failed to open queue manager '" + vstrQueueManager + "' when trying to " + strTypeOfOperation + ".",
e,
WebExceptionStatus.ConnectFailure,
null);
}
}
return objQueueManager;
}
/// <summary>WSAltTransports.MQWebRequest.GetQManager</summary>
/// <author>Simon G</author>
/// <date>24 February 2003</date>
/// <remarks>
/// Given a Queue Manager instance and a Queue name,
/// gets an object representing the Queue if it exists
/// </remarks>
/// <param name="vobjQueueManager" type="MQAX200.MQQueueManager">Queue Manager reference</param>
/// <param name="vstrQueue" type="string">Queue name</param>
/// <param name="vblnIsRequest" type="System.bool">true if this is a request</param>
/// <returns type="MQAX200.MQQueue">Queue instance</returns>
/// <preconditions>Valid Queue Manager object</preconditions>
/// <postconditions>Queue instance returned or exception thrown</postconditions>
private MQAX200.MQQueue GetQ(
MQAX200.MQQueueManager vobjQueueManager,
string vstrQueueName,
bool vblnIsRequest)
{
MQAX200.MQQueue objQueue = null;
try
{
// Set up access flags
int intReqFlags = (vblnIsRequest == true) ? (int)MQAX200.MQ.MQOO_OUTPUT : (int)MQAX200.MQ.MQOO_INQUIRE + (int)MQAX200.MQ.MQOO_INPUT_AS_Q_DEF;
// Ask the Queue Manager to open the Queue
objQueue = (MQAX200.MQQueue)vobjQueueManager.AccessQueue(
vstrQueueName,
intReqFlags,
string.Empty,
string.Empty,
string.Empty);
}
catch(Exception e)
{
// Failed to get Queue manager - report this. It could be for one of several
// reasons:
//
// * The Queue cannot be found (rememeber - case sentitive!)
// * General connect failure
string strTypeOfQueue = (vblnIsRequest == true) ? "request" : "response";
string strTypeOfOperation = (vblnIsRequest == true) ? "send request" : "read response";
if (e.Message.IndexOf("ReasonCode = 2085, ReasonName = MQRC_UNKNOWN_OBJECT_NAME") >= 0)
{
throw new WebException(
"Failed to locate MQ " + strTypeOfQueue + " queue '" + vstrQueueName + "' when trying to " + strTypeOfOperation + ".",
e,
WebExceptionStatus.NameResolutionFailure,
null);
}
else
{
throw new WebException(
"Failed to open MQ " + strTypeOfQueue + " queue '" + vstrQueueName + "' when trying to " + strTypeOfOperation + ".",
e,
WebExceptionStatus.ConnectFailure,
null);
}
}
return objQueue;
}
}
/// <summary>WSAltTransports.MQWebResponse</summary>
/// <author>SGregory</author>
/// <date>16 January 2003</date>
/// <remarks>
/// The MQ Response - we totally derive from MyBaseWebResponse here
/// </remarks>
public sealed class MQWebResponse : MyBaseWebResponse
{
/// <summary>WSAltTransports.MQWebResponse.MQWebResponse</summary>
/// <author>Simon G</author>
/// <date>24 February 2003</date>
/// <remarks>Default constructor - used simply to set up the base class member data</remarks>
internal MQWebResponse()
{
}
/// <summary>WSAltTransports.MQWebResponse.MQWebResponse</summary>
/// <author>Simon G</author>
/// <date>24 February 2003</date>
/// <remarks>Error information constructor</remarks>
/// <param name="vintStatusCode" type="int">WebResponse Code e.g. 500</param>
/// <param name="vstrStatusDescription" type="string">WebResponse Error description</param>
/// <param name="vstrLog" type="string">Associated stack trace or similar</param>
/// <postconditions>Error information set up</postconditions>
internal MQWebResponse(
int vintStatusCode,
string vstrStatusDescription,
string vstrLog)
{
// Delegate to the base class for the setting up of the
// error information. This is explicitly shown here through
// the use of "base."
base.SetErrorInformation(
vintStatusCode,
vstrStatusDescription,
vstrLog);
}
}
}