/** * Construct an EbXmlMessage for sending. * * @param s An SdSTransmissionDetails instance for recipient information * @param m SpineHL7Message instance to be sent * @param c Reference to an SDSconnection for resolving URLs */ public EbXmlMessage(SdsTransmissionDetails s, SpineHL7Message m) { SDSconnection c = ConnectionManager.getInstance().SdsConnection; odsCode = s.Org; header = new EbXmlHeader(this, s); type = EBXML; hl7message = m; string svcurl = c.resolveUrl(s.SvcIA); if (svcurl == null) { resolvedUrl = s.Url; } else { resolvedUrl = svcurl; } if (s.Retries != SdsTransmissionDetails.NOT_SET) { retryCount = s.Retries; minRetryInterval = s.RetryInterval; persistDuration = s.PersistDuration; } attachments = new List <Attachment>(); persistable = (s.Retries > 0); }
/** * Instantiate the SDSconnection, reading configuration details from the Registry. There is an * additional dependency on an external PKCS#12 (.pfx) file that contains the Spine endpoint * certificate and key. */ public SDSconnection() { server = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, SDS_SERVER_REGVAL, ""); cacheDir = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, SDS_CACHE_FILE_REGVAL, ""); certificateFile = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, SDS_CERT_FILE_REGVAL, ""); urlResolverFile = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, URL_RESOLVER_FILE, ""); myAsid = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, MY_ASID, ""); myPartyKey = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, MY_PARTY_KEY, ""); EbXmlHeader.setMyPartyKey(myPartyKey); string s = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, SDS_REFRESH_PERIOD_REGVAL, ""); loadUrlResolver(); if (s.Length > 0) { try { cacheRefreshPeriod = Int32.Parse(s); } catch (Exception) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sb = new StringBuilder("Registry entry "); sb.Append(SDS_REFRESH_PERIOD_REGVAL); sb.Append(" has invalid non-integer value '"); sb.Append(s); sb.Append("'. Using default value "); sb.Append(DEFAULT_REFRESH_PERIOD); sb.Append(" hours instead"); logger.WriteEntry(sb.ToString(), EventLogEntryType.Warning); } } s = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, SDS_OPENTEST_REGVAL, ""); opentest = (s.ToUpper().StartsWith("Y")); serviceRoot = (opentest) ? OPENTEST_SERVICES_ROOT : CIS_SERVICES_ROOT; if (server.Length == 0) { if (cacheDir.Length == 0) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("Neither LDAP server name nor cache file registry entries are set - cannot resolve anything!", EventLogEntryType.Error); } else { cache = new SDScache(cacheDir, cacheRefreshPeriod); } } else { if (cacheDir.Length == 0) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("Cache file registry entry not set: all queries will be resolved directly from SDS and this MAY impact performance", EventLogEntryType.Warning); } else { cache = new SDScache(cacheDir, cacheRefreshPeriod); } } if (certificateFile.Length > 0) { try { String certPass = (string)Registry.GetValue(SDS_CONNECTION_REGSITRY_KEY, SDS_CERT_PASS_REGVAL, ""); certificate = new X509Certificate(certificateFile, certPass); } catch (Exception e) { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; StringBuilder sb = new StringBuilder("Certificate "); sb.Append(certificateFile); sb.Append(" failed to load: "); sb.Append(e.ToString()); sb.Append(" - no lookups can be made against SDS"); logger.WriteEntry(sb.ToString(), EventLogEntryType.Error); } } else { EventLog logger = new EventLog("Application"); logger.Source = LOGSOURCE; logger.WriteEntry("No certificate provided - queries will be resolved from cache only", EventLogEntryType.Warning); } }
/** * Used to assemble an EbXmlMessage from the given Stream, which has been accepted from the network. * This expects to get the inbound HTTP stream (or at least an SslStream) from the start, because it * begins processing with the HTTP POST. */ public EbXmlMessage(Stream instream) { // Assemble from network - note that Spine messages are NOT chunked // string headerline = null; Dictionary <string, string> headers = new Dictionary <string, string>(); while ((headerline = readLine(instream)).Trim().Length != 0) { if (headerline.StartsWith("POST")) { int firstSpace = headerline.IndexOf(' '); int secondSpace = headerline.IndexOf(' ', firstSpace + 1); if ((firstSpace == -1) || (secondSpace == -1)) { throw new Exception("Malformed HTTP request line, can't parse POST context path"); } receivedContextPath = headerline.Substring(firstSpace, secondSpace - firstSpace); } else { int colon = headerline.IndexOf(":"); if (colon == -1) { throw new Exception("Malformed HTTP header - no field/data delimiter colon"); } headers.Add(headerline.Substring(0, colon).ToUpper(), headerline.Substring(colon + 1).Trim()); } } string ctype = headers["CONTENT-TYPE"]; if (ctype == null) { throw new Exception("Malformed HTTP headers - no Content-Type found"); } if (ctype.Contains("multipart/related")) { mimeboundary = setMimeBoundary(ctype); } receivedHost = headers["HOST"]; string clen = headers["CONTENT-LENGTH"]; if (clen == null) { throw new Exception("Malformed HTTP headers - no Content-Length found"); } int contentlength = Int32.Parse(clen); soapAction = headers["SOAPACTION"]; soapAction = soapAction.Replace('"', ' ').Trim(); // There is a bug in Spine-hosted services that turns a SOAPaction starting with // "urn:" into "urn:urn:" - fix this if we find it. // if (soapAction.StartsWith("urn:urn:")) { soapAction = soapAction.Substring(4); } // Read content-length bytes and parse out the various parts of the // received message. // TODO: Put in proper handling. // Byte[] wire = new Byte[contentlength]; int bytesRead = 0; while (bytesRead < contentlength) { bytesRead += instream.Read(wire, bytesRead, contentlength - bytesRead); } string msg = Encoding.UTF8.GetString(wire); // Split on the mimeboundary. "msg" doesn't contain the HTTP headers so we should // just be able to walk through the attachments. If we can't, report an exception // int startPosition = msg.IndexOf(mimeboundary, 0); if (startPosition == -1) { // Need to handle the case where the content is // actually an asynchronous ebXML ack. // // If content-type is text/xml and soapaction contains Acknowledgment or MessageError, // it is an ack/nack. If we get one of these we need to log it and tell the connection // manager about it. But we don't need to do any further processing. // if (ctype.ToLower().StartsWith("text/xml")) { if (soapAction.Contains("Acknowledgment")) { // Remove from requests, and exit string a = EbXmlAcknowledgment.getAckedMessageId(msg); if (a == null) { EventLog logger = new EventLog("Application"); logger.Source = ConnectionManager.LOGSOURCE; StringBuilder sb = new StringBuilder("Failed to extract message id reference from Acknowledgment: "); sb.Append(msg); logger.WriteEntry(sb.ToString(), EventLogEntryType.Error); return; } ConnectionManager cm = ConnectionManager.getInstance(); cm.registerAck(a); return; } if (soapAction.Contains("MessageError")) { // Remove from requests, and exit string a = EbXmlAcknowledgment.getAckedMessageId(msg); if (a == null) { EventLog logger = new EventLog("Application"); logger.Source = ConnectionManager.LOGSOURCE; StringBuilder sb = new StringBuilder("Failed to extract message id reference from MessageError: "); sb.Append(msg); logger.WriteEntry(sb.ToString(), EventLogEntryType.Error); return; } EventLog l = new EventLog("Application"); l.Source = ConnectionManager.LOGSOURCE; StringBuilder sbe = new StringBuilder("EbXML MessageError received: "); sbe.Append(msg); l.WriteEntry(sbe.ToString(), EventLogEntryType.Error); ConnectionManager cm = ConnectionManager.getInstance(); cm.registerAck(a); return; } } throw new Exception("Malformed message"); } int endPosition = 0; int partCount = 0; bool gotEndBoundary = false; do { startPosition += mimeboundary.Length; endPosition = msg.IndexOf(mimeboundary, startPosition); if (endPosition == -1) { gotEndBoundary = true; } else { switch (partCount) { case 0: header = new EbXmlHeader(msg.Substring(startPosition, endPosition - startPosition)); if (header.Timestamp != null) { started = DateTime.Parse(header.Timestamp); // We don't know how many attempts were actually made, so assume // only one try at the start time. // lastTry = DateTime.Parse(header.Timestamp); tries = 1; } break; case 1: hl7message = new SpineHL7Message(msg.Substring(startPosition, endPosition - startPosition)); break; default: if (attachments == null) { attachments = new List <Attachment>(); } // IMPROVEMENT: Make this more flexible to be able to support multiple types of // ITK trunk message, just in case // if (soapAction.Contains("COPC_IN000001GB01")) { attachments.Add(new ITKDistributionEnvelopeAttachment(msg.Substring(startPosition, endPosition - startPosition))); } else { attachments.Add(new GeneralAttachment(msg.Substring(startPosition, endPosition - startPosition))); } break; } partCount++; startPosition = endPosition; } }while (!gotEndBoundary); // persistDuration = (int)(ConnectionManager.getInstance().getPersistDuration(header.SvcIA).TotalSeconds); }
/** * Used to assemble an EbXmlMessage from the given Stream, which has been accepted from the network. * This expects to get the inbound HTTP stream (or at least an SslStream) from the start, because it * begins processing with the HTTP POST. */ public EbXmlMessage(Stream instream) { // Assemble from network - note that Spine messages are NOT chunked // string headerline = null; Dictionary<string, string> headers = new Dictionary<string, string>(); while ((headerline = readLine(instream)).Trim().Length != 0) { if (headerline.StartsWith("POST")) { int firstSpace = headerline.IndexOf(' '); int secondSpace = headerline.IndexOf(' ', firstSpace + 1); if ((firstSpace == -1) || (secondSpace == -1)) throw new Exception("Malformed HTTP request line, can't parse POST context path"); receivedContextPath = headerline.Substring(firstSpace, secondSpace - firstSpace); } else { int colon = headerline.IndexOf(":"); if (colon == -1) { throw new Exception("Malformed HTTP header - no field/data delimiter colon"); } headers.Add(headerline.Substring(0, colon).ToUpper(), headerline.Substring(colon + 1).Trim()); } } string ctype = headers["CONTENT-TYPE"]; if (ctype == null) { throw new Exception("Malformed HTTP headers - no Content-Type found"); } if (ctype.Contains("multipart/related")) { mimeboundary = setMimeBoundary(ctype); } receivedHost = headers["HOST"]; string clen = headers["CONTENT-LENGTH"]; if (clen == null) { throw new Exception("Malformed HTTP headers - no Content-Length found"); } int contentlength = Int32.Parse(clen); soapAction = headers["SOAPACTION"]; soapAction = soapAction.Replace('"', ' ').Trim(); // There is a bug in Spine-hosted services that turns a SOAPaction starting with // "urn:" into "urn:urn:" - fix this if we find it. // if (soapAction.StartsWith("urn:urn:")) soapAction = soapAction.Substring(4); // Read content-length bytes and parse out the various parts of the // received message. // TODO: Put in proper handling. // Byte[] wire = new Byte[contentlength]; int bytesRead = 0; while (bytesRead < contentlength) { bytesRead += instream.Read(wire, bytesRead, contentlength - bytesRead); } string msg = Encoding.UTF8.GetString(wire); // Split on the mimeboundary. "msg" doesn't contain the HTTP headers so we should // just be able to walk through the attachments. If we can't, report an exception // int startPosition = msg.IndexOf(mimeboundary, 0); if (startPosition == -1) { // Need to handle the case where the content is // actually an asynchronous ebXML ack. // // If content-type is text/xml and soapaction contains Acknowledgment or MessageError, // it is an ack/nack. If we get one of these we need to log it and tell the connection // manager about it. But we don't need to do any further processing. // if (ctype.ToLower().StartsWith("text/xml")) { if (soapAction.Contains("Acknowledgment")) { // Remove from requests, and exit string a = EbXmlAcknowledgment.getAckedMessageId(msg); if (a == null) { EventLog logger = new EventLog("Application"); logger.Source = ConnectionManager.LOGSOURCE; StringBuilder sb = new StringBuilder("Failed to extract message id reference from Acknowledgment: "); sb.Append(msg); logger.WriteEntry(sb.ToString(), EventLogEntryType.Error); return; } ConnectionManager cm = ConnectionManager.getInstance(); cm.registerAck(a); return; } if (soapAction.Contains("MessageError")) { // Remove from requests, and exit string a = EbXmlAcknowledgment.getAckedMessageId(msg); if (a == null) { EventLog logger = new EventLog("Application"); logger.Source = ConnectionManager.LOGSOURCE; StringBuilder sb = new StringBuilder("Failed to extract message id reference from MessageError: "); sb.Append(msg); logger.WriteEntry(sb.ToString(), EventLogEntryType.Error); return; } EventLog l = new EventLog("Application"); l.Source = ConnectionManager.LOGSOURCE; StringBuilder sbe = new StringBuilder("EbXML MessageError received: "); sbe.Append(msg); l.WriteEntry(sbe.ToString(), EventLogEntryType.Error); ConnectionManager cm = ConnectionManager.getInstance(); cm.registerAck(a); return; } } throw new Exception("Malformed message"); } int endPosition = 0; int partCount = 0; bool gotEndBoundary = false; do { startPosition += mimeboundary.Length; endPosition = msg.IndexOf(mimeboundary, startPosition); if (endPosition == -1) { gotEndBoundary = true; } else { switch (partCount) { case 0: header = new EbXmlHeader(msg.Substring(startPosition, endPosition - startPosition)); if (header.Timestamp != null) { started = DateTime.Parse(header.Timestamp); // We don't know how many attempts were actually made, so assume // only one try at the start time. // lastTry = DateTime.Parse(header.Timestamp); tries = 1; } break; case 1: hl7message = new SpineHL7Message(msg.Substring(startPosition, endPosition - startPosition)); break; default: if (attachments == null) attachments = new List<Attachment>(); // IMPROVEMENT: Make this more flexible to be able to support multiple types of // ITK trunk message, just in case // if (soapAction.Contains("COPC_IN000001GB01")) { attachments.Add(new ITKDistributionEnvelopeAttachment(msg.Substring(startPosition, endPosition - startPosition))); } else { attachments.Add(new GeneralAttachment(msg.Substring(startPosition, endPosition - startPosition))); } break; } partCount++; startPosition = endPosition; } } while (!gotEndBoundary); // persistDuration = (int)(ConnectionManager.getInstance().getPersistDuration(header.SvcIA).TotalSeconds); }
/** * Construct an EbXmlMessage for sending. * * @param s An SdSTransmissionDetails instance for recipient information * @param m SpineHL7Message instance to be sent * @param c Reference to an SDSconnection for resolving URLs */ public EbXmlMessage(SdsTransmissionDetails s, SpineHL7Message m) { SDSconnection c = ConnectionManager.getInstance().SdsConnection; odsCode = s.Org; header = new EbXmlHeader(this, s); type = EBXML; hl7message = m; string svcurl = c.resolveUrl(s.SvcIA); if (svcurl == null) resolvedUrl = s.Url; else resolvedUrl = svcurl; if (s.Retries != SdsTransmissionDetails.NOT_SET) { retryCount = s.Retries; minRetryInterval = s.RetryInterval; persistDuration = s.PersistDuration; } attachments = new List<Attachment>(); persistable = (s.Retries > 0); }