/** * Checks to see if the given EbXmlMessage is acknowledged asynchronously, and just returns * if not. Otherwise, constructs an EbXMLAcknowledgment addressed to the sender of "msg", * and calls the ConnectionManager to send it. * * @param EbXmlMessage to acknowledge */ private void doAsynchronousAck(EbXmlMessage msg) { if (msg == null) { return; } if (!msg.Header.DuplicateElimination) { return; } if (msg.Header.SyncReply) { return; } StringBuilder a = new StringBuilder(makeEbXmlAck(msg, false)); ConnectionManager cm = ConnectionManager.getInstance(); SDSconnection sds = cm.SdsConnection; string ods = msg.Header.FromPartyKey.Substring(0, msg.Header.FromPartyKey.IndexOf("-")); List <SdsTransmissionDetails> sdsdetails = sds.getTransmissionDetails(ACKSERVICE, ods, null, msg.Header.FromPartyKey); a.Replace("__CPA_ID__", sdsdetails[0].CPAid); EbXmlAcknowledgment ack = new EbXmlAcknowledgment(a.ToString()); ack.setHost(sdsdetails[0].Url); cm.send(ack, sdsdetails[0]); }
/** Called by the Listener to process a received message. See if this * is a response and correlate with requests if it is, otherwise add * to the received messages queue. This is called by Listener.processMessage() * which is invoked in its own thread, so we don't need to spawn any new * threads here. * * @param Received message */ internal void receive(Sendable s) { if (s.SoapAction.Contains("service:Acknowledgment") || s.SoapAction.Contains("service:MessageError")) { // Asynchronous ack/nack. Remove from request list and exit. // EbXmlMessage m = (EbXmlMessage)s; if (!requests.Remove(m.Header.ConversationId)) { // Log receipt of ack/nack that doesn't belong to us... // Note: This may be legitimate in a clustered MHS or if in- and out-bound // nodes are separate. // EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sbe = new StringBuilder("Unexpected response "); sbe.Append(s.SoapAction); sbe.Append(" with conversation id "); sbe.Append(m.Header.ConversationId); sbe.Append(" that was not sent from here."); logger.WriteEntry(sbe.ToString(), EventLogEntryType.Information); } depersist(m.Header.ConversationId); return; } ISpineHandler h = null; try { h = handlers[s.SoapAction]; } catch (KeyNotFoundException) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sbe = new StringBuilder("Unknown SOAP action "); sbe.Append(s.SoapAction); sbe.Append(" using DefaultFileSaveSpineHandler"); logger.WriteEntry(sbe.ToString(), EventLogEntryType.FailureAudit); h = defaultSpineHandler; } try { h.handle(s); } catch (Exception e) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sbe = new StringBuilder("Exception handling "); sbe.Append(s.SoapAction); sbe.Append(" : "); sbe.Append(e.ToString()); logger.WriteEntry(sbe.ToString(), EventLogEntryType.FailureAudit); } }
/** * Removes a reliable request from the retry list. Does nothing if null is passed * or if the message id is not known to the retry list. * * @param a Message id of the request to remove. */ internal void removeRequest(EbXmlMessage a) { if (a == null) { return; } string m = a.getMessageId(); if (requests.ContainsKey(m)) { requests.Remove(m); depersist(a); } }
/** * Checks to see if the EbXmlMessage should be acknowledged synchronously, and just returns * a zero-length string if not. Otherwise, makes an ebXML acknowledgment and returns it as * a string. * * @param EbXmlMessage to acknowledge */ private string makeSynchronousAck(EbXmlMessage msg) { if (!msg.Header.DuplicateElimination) { return(EMPTY_RESPONSE); } if (!msg.Header.SyncReply) { return(EMPTY_RESPONSE); } StringBuilder hdr = new StringBuilder(ACK_HTTP_HEADER); string ack = makeEbXmlAck(msg, true); hdr.Replace("__CONTENT_LENGTH__", ack.Length.ToString()); hdr.Append(ack); return(hdr.ToString()); }
/** * TODO: Constructs an asynchronous ebXML error message. Where do we get error information from ? * * @param EbXml message to NACK */ private void doAsynchronousNack(EbXmlMessage msg) { // If "msg" is an ack or message error if (msg.Header == null) { return; } // If "msg" is "unreliable" if (!msg.Header.DuplicateElimination) { return; } // If we've already returned the ack synchronously if (msg.Header.SyncReply) { return; } }
/** * Assembles an ebXML acknowledgment. */ private string makeEbXmlAck(EbXmlMessage msg, bool replaceCpaId) { StringBuilder sb = new StringBuilder(ebxmlacktemplate); sb.Replace("__FROM_PARTY_ID__", msg.Header.FromPartyKey); //sb.Replace("__FROM_PARTY_ID__", msg.Header.ToPartyKey); string mp = ConnectionManager.getInstance().SdsConnection.MyPartyKey; sb.Replace("__TO_PARTY_ID__", mp); sb.Replace("__ORIGINAL_MESSAGE_ID__", msg.getMessageId()); sb.Replace("__CONVERSATION_ID__", msg.Header.ConversationId); if (replaceCpaId) { sb.Replace("__CPA_ID__", msg.Header.CpaId); } sb.Replace("__ACK_MESSAGE_ID__", System.Guid.NewGuid().ToString().ToUpper()); sb.Replace("__ACK_TIMESTAMP__", DateTime.Now.ToString(EbXmlHeader.ISO8601DATEFORMAT)); return(sb.ToString()); }
private void depersist(EbXmlMessage a) { string pfile = messageDirectory + a.getOdsCode() + "_" + a.getMessageId(); try { if (File.Exists(pfile)) { File.Delete(pfile); } } catch (Exception e) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sbe = new StringBuilder("Unexpected error "); sbe.Append(e.ToString()); sbe.Append(" de-persisting message "); sbe.Append(pfile); logger.WriteEntry(sbe.ToString(), EventLogEntryType.Error); } }
/** Called at start-up if the system needs to load any persisted, reliable messages * for sending. This applies the persist duration for the message type to the declared * timestamp, and will run the expire() method on anything that has expired whilst * the MHS was down. */ public void loadPersistedMessages() { string[] files = Directory.GetFiles(messageDirectory); EbXmlMessage ebxml = null; foreach (String f in files) { using (FileStream fs = new FileStream(f, FileMode.Open)) { try { ebxml = new EbXmlMessage(fs); // "f" is now of the form "odscode_messageid" so get the ods code and set it here. string ods = f.Substring(0, f.IndexOf("_")).Substring(f.LastIndexOf("\\") + 1); ebxml.setOdsCode(ods); // Do an SDS lookup and populate retry interval, persist duration // and retry cound in ebxml. List <SdsTransmissionDetails> sdsdetails = sdsConnection.getTransmissionDetails(ebxml.Header.SvcIA, ods, ebxml.HL7Message.ToAsid, ebxml.Header.ToPartyKey); if (sdsdetails.Count == 0) { throw new Exception("Cannot resolve SDS details for persisted message: " + f); } if (sdsdetails.Count > 1) { throw new Exception("Ambiguous SDS details for persisted message: " + f); } SdsTransmissionDetails sds = sdsdetails[0]; ebxml.PersistDuration = sds.PersistDuration; ebxml.RetryCount = sds.Retries; ebxml.RetryInterval = sds.RetryInterval; } catch (Exception e) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sbe = new StringBuilder("Failed to load persisted ebXml message "); sbe.Append(f); sbe.Append(" due to exception "); sbe.Append(e.Message); sbe.Append(" at "); sbe.Append(e.StackTrace); logger.WriteEntry(sbe.ToString(), EventLogEntryType.FailureAudit); continue; } } DateTime check = DateTime.Now; TimeSpan pd = getPersistDuration(ebxml.Header.SvcIA); DateTime expiryTime = ebxml.Started.Add(pd); if (expiryTime.CompareTo(check) < 0) { depersist(ebxml); ebxml.Expire(); } else { requests.Add(ebxml.getMessageId(), ebxml); } } }
/** * Called in its own thread to process the inbound message on the given socket. Reads the * message, returns any synchronous or asynchronous acknowledgment, and logs the message id in the de-duplication list. * If the id has not been seen before (according to the list), calls ConnectionManager.receive(). * * @param Clear-text accepted socket */ private void processMessage(Socket s) { // Log received ebXML message ids and add to it where "duplicate // elimination" is set. Do this in memory - if we go down between retries on long- // duration interactions we'll incorrectly not detect the duplicate, but that will // be a recognised limitation for now, and is supposed to be caught by HL7 de- // duplication anyway. // EbXmlMessage msg = null; try { ConnectionManager cm = ConnectionManager.getInstance(); NetworkStream n = new NetworkStream(s); SslStream ssl = new SslStream(n, false, validateRemoteCertificate, getLocalCertificate); // FIXME: Require client certificate - but leave it for now // ssl.AuthenticateAsServer(cm.getCertificate()); msg = new EbXmlMessage(ssl); string ack = makeSynchronousAck(msg); if (ack != null) { ssl.Write(Encoding.UTF8.GetBytes(ack)); ssl.Flush(); } ssl.Close(); // If we have data for it, DateTime.Now + persistDuration. The trouble is that // the persistDuration isn't actually carried in the message. Easiest is *probably* // to cache SDS details for "my party key" for all received messages, which will // require a "tool" to do the calls. // Dictionary <string, TimeSpan> pd = cm.ReceivedPersistDurations; if (pd != null) { bool notSeenBefore = true; lock (requestLock) { notSeenBefore = (!receivedIds.ContainsKey(msg.Header.MessageId)); } if (notSeenBefore) { try { // TimeSpan ts = pd[msg.SoapAction]; TimeSpan ts = pd[msg.Header.SvcIA]; DateTime expireTime = DateTime.Now; expireTime = expireTime.Add(ts); lock (requestLock) { receivedIds.Add(msg.Header.MessageId, expireTime); } } catch (KeyNotFoundException) { } cm.receive(msg); } // No "else" - if the message id is in the "receivedIds" set then we want // to do the ack but nothing else. } else { cm.receive(msg); } } catch (Exception e) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sb = new StringBuilder("Error receiving message from"); sb.Append(s.RemoteEndPoint.ToString()); sb.Append(" : "); sb.Append(e.ToString()); logger.WriteEntry(sb.ToString(), EventLogEntryType.Error); if (msg != null) { doAsynchronousNack(msg); } return; } doAsynchronousAck(msg); }
static void Main(string[] args) { ConnectionManager cm = ConnectionManager.getInstance(); SDSconnection c = cm.SdsConnection; cm.listen(); // cm.loadPersistedMessages(); // List<SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:pdsquery:QUPA_IN000008UK02", "YES", null, null); // / List<SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:pdsquery:QUPA_IN020000UK31", "YEA", "631955299542", null); // List<SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:mm:PORX_IN020101UK31", "YEA", "631955299542", null); /* string qstring = null; * using (StreamReader rdr = File.OpenText(@"c:\test\data\query.txt")) * { * qstring = rdr.ReadToEnd(); * } * // SpineHL7Message msg = new SpineHL7Message("QUPA_IN020000UK31", qstring); * //SpineHL7Message msg = new SpineHL7Message("PORX_IN020101UK31", qstring); * SpineHL7Message msg = new SpineHL7Message("QUPA_IN000008UK02", qstring); * SpineSOAPRequest req = new SpineSOAPRequest(sdsdetails[0], msg); * msg.ToAsid = sdsdetails[0].Asid[0]; * msg.MyAsid = c.MyAsid; * msg.IsQuery = true; * //EbXmlMessage eb = new EbXmlMessage(sdsdetails[0], msg, c); * * MemoryStream ms = new MemoryStream(); * req.write(ms); * //eb.write(ms); * ms.Seek(0, SeekOrigin.Begin); * StreamReader sr = new StreamReader(ms); * string s = sr.ReadToEnd(); * * cm.send(req, sdsdetails[0]); */ //cm.listen(); // while (true) ; // List<SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:pdsquery:QUPA_IN000008UK02", "YES", null, null); // List<SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:pdsquery:QUPA_IN040000UK32", "YES", null, null); // List<SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:mm:PORX_IN132004UK30", "YES", null, null); List <SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:psis:REPC_IN150015UK05", "YES", null, null); // List<SdsTransmissionDetails> sdsdetails = c.getTransmissionDetails("urn:nhs:names:services:pdsquery:QUPA_IN000006UK02", "YES", null, null); int i = sdsdetails.Count; cm.listen(); string qstring = null; using (StreamReader rdr = File.OpenText(@"c:\test\data\REPC_IN150015UK05_mhstest.xml")) { qstring = rdr.ReadToEnd(); } SpineHL7Message msg = new SpineHL7Message("REPC_IN150015UK05", qstring); msg.ToAsid = sdsdetails[0].Asid[0]; msg.MyAsid = c.MyAsid; msg.AuthorRole = "S0080:G0450:R5080"; msg.AuthorUid = "687227875014"; msg.AuthorUrp = "012345678901"; EbXmlMessage eb = new EbXmlMessage(sdsdetails[0], msg); // eb.Attachments.Add(deattachment); MemoryStream ms = new MemoryStream(); eb.write(ms); ms.Seek(0, SeekOrigin.Begin); StreamReader sr = new StreamReader(ms); string s = sr.ReadToEnd(); cm.send(eb, sdsdetails[0]); }
private void depersist(EbXmlMessage a) { string pfile = messageDirectory + a.getOdsCode() + "_" + a.getMessageId(); try { if (File.Exists(pfile)) File.Delete(pfile); } catch (Exception e) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sbe = new StringBuilder("Unexpected error "); sbe.Append(e.ToString()); sbe.Append(" de-persisting message "); sbe.Append(pfile); logger.WriteEntry(sbe.ToString(), EventLogEntryType.Error); } }
/** * Removes a reliable request from the retry list. Does nothing if null is passed * or if the message id is not known to the retry list. * * @param a Message id of the request to remove. */ internal void removeRequest(EbXmlMessage a) { if (a == null) return; string m = a.getMessageId(); if (requests.ContainsKey(m)) { requests.Remove(m); depersist(a); } }
/** Called at start-up if the system needs to load any persisted, reliable messages * for sending. This applies the persist duration for the message type to the declared * timestamp, and will run the expire() method on anything that has expired whilst * the MHS was down. */ public void loadPersistedMessages() { string[] files = Directory.GetFiles(messageDirectory); EbXmlMessage ebxml = null; foreach (String f in files) { using (FileStream fs = new FileStream(f, FileMode.Open)) { try { ebxml = new EbXmlMessage(fs); // "f" is now of the form "odscode_messageid" so get the ods code and set it here. string ods = f.Substring(0, f.IndexOf("_")).Substring(f.LastIndexOf("\\") + 1); ebxml.setOdsCode(ods); // Do an SDS lookup and populate retry interval, persist duration // and retry cound in ebxml. List<SdsTransmissionDetails> sdsdetails = sdsConnection.getTransmissionDetails(ebxml.Header.SvcIA, ods, ebxml.HL7Message.ToAsid, ebxml.Header.ToPartyKey); if (sdsdetails.Count == 0) throw new Exception("Cannot resolve SDS details for persisted message: " + f); if (sdsdetails.Count > 1) throw new Exception("Ambiguous SDS details for persisted message: " + f); SdsTransmissionDetails sds = sdsdetails[0]; ebxml.PersistDuration = sds.PersistDuration; ebxml.RetryCount = sds.Retries; ebxml.RetryInterval = sds.RetryInterval; } catch(Exception e) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sbe = new StringBuilder("Failed to load persisted ebXml message "); sbe.Append(f); sbe.Append(" due to exception "); sbe.Append(e.Message); sbe.Append(" at "); sbe.Append(e.StackTrace); logger.WriteEntry(sbe.ToString(), EventLogEntryType.FailureAudit); continue; } } DateTime check = DateTime.Now; TimeSpan pd = getPersistDuration(ebxml.Header.SvcIA); DateTime expiryTime = ebxml.Started.Add(pd); if (expiryTime.CompareTo(check) < 0) { depersist(ebxml); ebxml.Expire(); } else { requests.Add(ebxml.getMessageId(), ebxml); } } }