/// <summary> /// Create an appropriate response given the results /// </summary> /// <param name="results">The results that matches the query</param> /// <param name="map">The HL7 query parameter mapping</param> /// <param name="request">The original request message</param> /// <param name="count">The number of results that the user requested</param> /// <param name="offset">The offset to the first result</param> /// <param name="queryId">The unique query identifier used</param> /// <returns>The constructed result message</returns> protected virtual IMessage CreateQueryResponse(Hl7MessageReceivedEventArgs request, Expression filter, Hl7QueryParameterType map, IEnumerable results, Guid queryId, int offset, int count, int totalResults) { var retVal = this.CreateACK(map.ResponseType, request.Message, "AA", "Query Success"); var omsh = retVal.GetStructure("MSH") as MSH; var qak = retVal.GetStructure("QAK") as QAK; var odsc = retVal.GetStructure("DSC") as DSC; var oqpd = retVal.GetStructure("QPD") as QPD; DeepCopy.copy(request.Message.GetStructure("QPD") as ISegment, oqpd); omsh.MessageType.MessageCode.Value = "RSP"; omsh.MessageType.MessageStructure.Value = retVal.GetType().Name; omsh.MessageType.TriggerEvent.Value = map.ResponseTrigger; omsh.MessageType.MessageStructure.Value = map.ResponseTypeXml; qak.HitCount.Value = totalResults.ToString(); qak.HitsRemaining.Value = (totalResults - offset - count > 0 ? totalResults - offset - count : 0).ToString(); qak.QueryResponseStatus.Value = totalResults == 0 ? "NF" : "OK"; qak.ThisPayload.Value = results.OfType <Object>().Count().ToString(); if (ApplicationServiceContext.Current.GetService <Core.Services.IQueryPersistenceService>() != null && Int32.Parse(qak.HitsRemaining.Value) > 0) { odsc.ContinuationPointer.Value = queryId.ToString(); odsc.ContinuationStyle.Value = "RD"; } // Process results retVal = map.QueryHandler.AppendQueryResult(results, filter, retVal, request, map.ScoreConfiguration, offset); return(retVal); }
/// <summary> /// Perform an admission operation /// </summary> protected virtual IMessage PerformAdmit(Hl7MessageReceivedEventArgs e, Bundle insertBundle) { try { var patient = insertBundle.Item.OfType <Patient>().FirstOrDefault(it => it.Tags.Any(t => t.TagKey == ".v2.segment" && t.Value == "PID")); if (patient == null) { throw new ArgumentNullException(nameof(insertBundle), "Message did not contain a patient"); } var repoService = ApplicationServiceContext.Current.GetService <IRepositoryService <Bundle> >(); if (repoService == null) { throw new InvalidOperationException("Cannot find repository for Patient"); } insertBundle = repoService.Insert(insertBundle); AuditUtil.AuditCreate(Core.Auditing.OutcomeIndicator.Success, null, insertBundle.Item.ToArray()); // Create response message return(this.CreateACK(typeof(ACK), e.Message, "CA", $"{patient.Key} created")); } catch { AuditUtil.AuditUpdate(Core.Auditing.OutcomeIndicator.MinorFail, null, insertBundle.Item.ToArray()); throw; } }
/// <summary> /// Perform an update of the specified patient /// </summary> protected virtual IMessage PerformUpdate(Hl7MessageReceivedEventArgs e, Bundle updateBundle) { try { var patient = updateBundle.Item.OfType <Patient>().FirstOrDefault(it => it.Tags.Any(t => t.TagKey == ".v2.segment" && t.Value == "PID")); if (patient == null) { throw new ArgumentNullException(nameof(updateBundle), "Message did not contain a patient"); } else if (!patient.Key.HasValue) { throw new InvalidOperationException("Update can only be performed on existing patients. Ensure that a unique identifier exists on the update record"); } var repoService = ApplicationServiceContext.Current.GetService <IRepositoryService <Bundle> >(); if (repoService == null) { throw new InvalidOperationException("Cannot find repository for Patient"); } updateBundle = repoService.Save(updateBundle); AuditUtil.AuditUpdate(Core.Auditing.OutcomeIndicator.Success, null, updateBundle.Item.ToArray()); // Create response message return(this.CreateACK(typeof(ACK), e.Message, "CA", $"{patient.Key} updated")); } catch { AuditUtil.AuditUpdate(Core.Auditing.OutcomeIndicator.MinorFail, null, updateBundle.Item.ToArray()); throw; } }
/// <summary> /// Perform an admission operation /// </summary> protected virtual IMessage PerformAdmit(Hl7MessageReceivedEventArgs e, Bundle insertBundle) { try { var patient = insertBundle.Item.OfType <Patient>().FirstOrDefault(it => it.Tags.Any(t => t.TagKey == "$v2.segment" && t.Value == "PID")); if (patient == null) { this.m_traceSource.TraceError("Message did not contain a patient"); throw new ArgumentNullException(nameof(insertBundle), this.m_localizationService.GetString("error.type.ArgumentNullException.missingPatient")); } insertBundle = this.m_bundleService.Insert(insertBundle); this.SendAuditAdmit(OutcomeIndicator.Success, e.Message, insertBundle.Item.OfType <IdentifiedData>()); // Create response message return(this.CreateACK(typeof(ACK), e.Message, "CA", $"{patient.Key} created")); } catch (Exception ex) { this.SendAuditAdmit(OutcomeIndicator.EpicFail, e.Message, null); this.m_traceSource.TraceError("Error performing admit"); throw new HL7ProcessingException(this.m_localizationService.GetString("error.messaging.hl7.messages.errorPerformingAdmit"), null, null, 0, 0, ex); } }
/// <summary> /// Perform an update of the specified patient /// </summary> protected virtual IMessage PerformUpdate(Hl7MessageReceivedEventArgs e, Bundle updateBundle) { try { var patient = updateBundle.Item.OfType <Patient>().FirstOrDefault(it => it.Tags.Any(t => t.TagKey == "$v2.segment" && t.Value == "PID")); if (patient == null) { this.m_traceSource.TraceError("Message did not contain a patient"); throw new ArgumentNullException(nameof(updateBundle), this.m_localizationService.GetString("error.type.ArgumentNullException.missingPatient")); } else if (!patient.Key.HasValue) { throw new InvalidOperationException("Update can only be performed on existing patients. Ensure that a unique identifier exists on the update record"); } updateBundle = this.m_bundleService.Save(updateBundle); this.SendAuditUpdate(Core.Auditing.OutcomeIndicator.Success, e.Message, updateBundle.Item.ToArray()); // Create response message return(this.CreateACK(typeof(ACK), e.Message, "CA", $"{patient.Key} updated")); } catch (Exception ex) { this.SendAuditUpdate(Core.Auditing.OutcomeIndicator.MinorFail, e.Message, updateBundle.Item.ToArray()); this.m_traceSource.TraceError("Error performing admit"); throw new HL7ProcessingException(this.m_localizationService.GetString("error.messaging.hl7.messages.errorPerformingAdmit"), null, null, 0, 0, ex); } }
/// <summary> /// Handles an update. /// </summary> /// <param name="message">The request.</param> /// <param name="evt">The <see cref="Hl7MessageReceivedEventArgs" /> instance containing the event data.</param> /// <returns>Returns the response message from the merge event.</returns> /// <exception cref="System.InvalidOperationException"></exception> internal IMessage HandlePixUpdate(NHapi.Model.V231.Message.ADT_A01 message, Hl7MessageReceivedEventArgs evt) { var dataService = ApplicationContext.Current.GetService <IPatientRepositoryService>(); // Create a details array var details = new List <IResultDetail>(); // Validate the inbound message MessageUtil.Validate(message, details); IMessage response = null; // Control if (message == null) { return(null); } try { // Create Query Data var data = MessageUtil.CreatePatient(message.MSH, message.EVN, message.PID, message.PD1, details); if (data == null) { throw new InvalidOperationException(ApplicationContext.Current.GetLocaleString("MSGE00A")); } var result = dataService.Save(data); if (result == null || result.VersionKey == null) { throw new InvalidOperationException(ApplicationContext.Current.GetLocaleString("DTPE001")); } //audit = auditUtil.CreateAuditData("ITI-8", result.VersionId.UpdateMode == UpdateModeType.Update ? ActionType.Update : ActionType.Create, OutcomeIndicator.Success, evt, new List<VersionedDomainIdentifier>() { result.VersionId }); // Now process the result response = MessageUtil.CreateNack(message, details, typeof(NHapi.Model.V25.Message.ACK)); MessageUtil.UpdateMSH(new NHapi.Base.Util.Terser(response), message); (response as NHapi.Model.V25.Message.ACK).MSH.MessageType.TriggerEvent.Value = message.MSH.MessageType.TriggerEvent.Value; (response as NHapi.Model.V25.Message.ACK).MSH.MessageType.MessageStructure.Value = "ACK"; } catch (Exception e) { this.traceSource.TraceEvent(TraceEventType.Error, 0, e.ToString()); if (!details.Exists(o => o.Message == e.Message || o.Exception == e)) { details.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); } response = MessageUtil.CreateNack(message, details, typeof(NHapi.Model.V25.Message.ACK)); } return(response); }
/// <summary> /// Handles the admit. /// </summary> /// <param name="message">The message.</param> /// <param name="eventArgs">The <see cref="Hl7MessageReceivedEventArgs"/> instance containing the event data.</param> /// <returns>IMessage.</returns> /// <exception cref="System.InvalidOperationException"> /// </exception> internal IMessage HandleAdmit(NHapi.Model.V231.Message.ADT_A01 message, Hl7MessageReceivedEventArgs eventArgs) { var patientRepositoryService = ApplicationContext.Current.GetService <IPatientRepositoryService>(); var details = new List <IResultDetail>(); MessageUtil.Validate(message, details); IMessage response; if (message == null) { return(null); } try { var patient = MessageUtil.CreatePatient(message.MSH, message.EVN, message.PID, message.PD1, details); if (details.Count(d => d.Type == ResultDetailType.Error) > 0) { throw new InvalidOperationException(ApplicationContext.Current.GetLocaleString("MSGE00A")); } var result = patientRepositoryService.Insert(patient); if (result?.VersionKey == null) { throw new InvalidOperationException(ApplicationContext.Current.GetLocaleString("DTPE001")); } response = MessageUtil.CreateNack(message, details, typeof(ACK)); MessageUtil.UpdateMSH(new Terser(response), message); (response as ACK).MSH.MessageType.TriggerEvent.Value = message.MSH.MessageType.TriggerEvent.Value; (response as ACK).MSH.MessageType.MessageStructure.Value = "ACK"; } catch (Exception e) { #if DEBUG this.traceSource.TraceEvent(TraceEventType.Error, 0, e.StackTrace); #endif this.traceSource.TraceEvent(TraceEventType.Error, 0, e.Message); if (!details.Exists(o => o.Message == e.Message || o.Exception == e)) { details.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); } response = MessageUtil.CreateNack(message, details, typeof(ACK)); } return(response); }
/// <summary> /// Handles an update. /// </summary> /// <param name="message">The ADT_A08 message.</param> /// <param name="eventArgs">The <see cref="Hl7MessageReceivedEventArgs" /> instance containing the event data.</param> /// <returns>Returns the response message from the merge event.</returns> internal IMessage HandlePixUpdate(NHapi.Model.V231.Message.ADT_A08 message, Hl7MessageReceivedEventArgs eventArgs) { var parser = new PipeParser(); message.MSH.MessageType.MessageStructure.Value = "ADT_A01"; var encodedMessage = parser.Parse(parser.Encode(message)); if (encodedMessage is NHapi.Model.V231.Message.ADT_A01) { return(this.HandlePixUpdate(encodedMessage as NHapi.Model.V231.Message.ADT_A01, eventArgs)); } return(MessageUtil.CreateNack(eventArgs.Message, "AR", "200", ApplicationContext.Current.GetLocaleString("MSGE074"))); }
/// <summary> /// Handles a merge. /// </summary> /// <param name="message">The ADT_A40 message.</param> /// <param name="e">The <see cref="Hl7MessageReceivedEventArgs"/> instance containing the event data.</param> /// <returns>Returns the response message from the merge event.</returns> private IMessage HandleMerge(NHapi.Model.V231.Message.ADT_A40 message, Hl7MessageReceivedEventArgs e) { var parser = new PipeParser(); message.MSH.MessageType.MessageStructure.Value = "ADT_A39"; var encodedMessage = parser.Parse(parser.Encode(message)); if (encodedMessage is NHapi.Model.V231.Message.ADT_A39) { return(this.HandleMerge(encodedMessage as NHapi.Model.V231.Message.ADT_A39, e)); } return(MessageUtil.CreateNack(e.Message, "AR", "200", ApplicationContext.Current.GetLocaleString("MSGE074"))); }
/// <summary> /// Handle the message generic handler /// </summary> /// <param name="e">The message event information</param> /// <returns>The result of the message handling</returns> public virtual IMessage HandleMessage(Hl7MessageReceivedEventArgs e) { try { this.Authenticate(e); if (!this.Validate(e.Message)) { throw new ArgumentException("Invalid message"); } return(this.HandleMessageInternal(e, MessageUtils.Parse(e.Message))); } catch (Exception ex) { this.m_traceSource.TraceEvent(EventLevel.Error, "Error processing message: {0}", ex); return(this.CreateNACK(typeof(ACK), e.Message, ex, e)); } }
/// <summary> /// Handle PIX Update /// </summary> private IMessage HandlePixUpdate(NHapi.Model.V231.Message.ADT_A08 aDT_A08, Hl7MessageReceivedEventArgs e) { ILocalizationService locale = this.Context.GetService(typeof(ILocalizationService)) as ILocalizationService; ISystemConfigurationService config = this.Context.GetService(typeof(ISystemConfigurationService)) as ISystemConfigurationService; PipeParser parser = new PipeParser(); aDT_A08.MSH.MessageType.MessageStructure.Value = "ADT_A01"; var message = parser.Parse(parser.Encode(aDT_A08)); if (message is NHapi.Model.V231.Message.ADT_A01) { return(this.HandlePixUpdate(message as NHapi.Model.V231.Message.ADT_A01, e)); } else { return(MessageUtil.CreateNack(e.Message, "AR", "200", locale.GetString("MSGE074"), config)); } }
/// <summary> /// Handle the message internally /// </summary> /// <param name="e">The message receive events</param> /// <param name="parsed">The parsed message</param> /// <returns>The response to the ADT message</returns> protected override IMessage HandleMessageInternal(Hl7MessageReceivedEventArgs e, Bundle parsed) { var msh = e.Message.GetStructure("MSH") as MSH; switch (msh.MessageType.TriggerEvent.Value) { case "A01": // Admit case "A04": // Register return(this.PerformAdmit(e, parsed)); // parsed.Item.OfType<Patient>().SingleOrDefault(o=>o.Tags.Any(t=>t.TagKey == ".v2.segment" && t.Value == "PID"))); case "A08": // Update return(this.PerformUpdate(e, parsed)); case "A40": // Merge return(this.PerformMerge(e, parsed)); default: throw new InvalidOperationException($"Do not understand event {msh.MessageType.TriggerEvent.Value}"); } }
/// <summary> /// Create query response for the data according to ITI-9 /// </summary> protected override IMessage CreateQueryResponse(Hl7MessageReceivedEventArgs request, Expression filter, Hl7QueryParameterType map, IEnumerable results, Guid queryId, int offset, int count, int totalResults) { var retVal = base.CreateQueryResponse(request, filter, map, results, queryId, offset, count, totalResults) as RSP_K23; // CASE 3: Domains are recognized but no results if (results.OfType <Patient>().Count() == 0) { retVal.MSA.AcknowledgmentCode.Value = "AE"; retVal.MSA.TextMessage.Value = "Query Error"; retVal.QAK.QueryResponseStatus.Value = "AE"; retVal.ERR.GetErrorLocation(0).SegmentID.Value = "QPD"; retVal.ERR.GetErrorLocation(0).SegmentSequence.Value = "1"; retVal.ERR.GetErrorLocation(0).FieldPosition.Value = "3"; retVal.ERR.GetErrorLocation(0).FieldRepetition.Value = "1"; retVal.ERR.GetErrorLocation(0).ComponentNumber.Value = "1"; retVal.ERR.HL7ErrorCode.Identifier.Value = "204"; retVal.ERR.HL7ErrorCode.Text.Value = "Unknown Key Identifier"; } return(retVal); }
/// <summary> /// Handle the message generic handler /// </summary> /// <param name="e">The message event information</param> /// <returns>The result of the message handling</returns> public virtual IMessage HandleMessage(Hl7MessageReceivedEventArgs e) { try { using (this.Authenticate(e)) { if (!this.Validate(e.Message)) { this.m_traceSource.TraceError("Invalid message"); throw new ArgumentException(this.m_localizationService.GetString("error.messaging.hl7.invalidMessage")); } var bundle = MessageUtils.Parse(e.Message); bundle.AddAnnotationToAll(SanteDBConstants.NoDynamicLoadAnnotation); return(this.HandleMessageInternal(e, bundle)); } } catch (Exception ex) { this.m_traceSource.TraceEvent(EventLevel.Error, "Error processing message: {0}", ex); return(this.CreateNACK(typeof(ACK), e.Message, ex, e)); } }
/// <summary> /// Performs a merge of the specified patient /// </summary> protected virtual IMessage PerformMerge(Hl7MessageReceivedEventArgs e, Bundle bundle) { // A merge should be parsed as a series of bundles within bundles representing the merge pairs... try { var mergePairs = bundle.Item.OfType <Bundle>(); if (!mergePairs.Any()) { this.m_traceSource.TraceError("Merge requires at least one pair of PID and MRG"); throw new InvalidOperationException(this.m_localizationService.GetString("error.messaging.hl7.messages.mergeMissingPair")); } foreach (var mrgPair in mergePairs) { var survivor = mrgPair.Item.OfType <Patient>().FirstOrDefault(o => o.GetTag("$v2.segment") == "PID"); var victims = mrgPair.Item.OfType <Patient>().Where(o => o.GetTag("$v2.segment") == "MRG"); if (survivor == null || !victims.Any()) { this.m_traceSource.TraceError("Merge requires at least one pair of PID and MRG"); throw new InvalidOperationException(this.m_localizationService.GetString("error.messaging.hl7.messages.mergeMissingPair")); } // Perform the merge this.SendAuditMerge(Core.Auditing.OutcomeIndicator.Success, e.Message, this.m_mergeService.Merge(survivor.Key.Value, victims.Select(o => o.Key.Value))); } return(this.CreateACK(typeof(ACK), e.Message, "CA", $"Merge accepted")); } catch (Exception ex) { this.SendAuditMerge(Core.Auditing.OutcomeIndicator.MinorFail, e.Message, null); this.m_traceSource.TraceError("Error performing merge"); throw new HL7ProcessingException(this.m_localizationService.GetString("error.messaging.hl7.messages.errorPerformingMerge"), null, null, 0, 0, ex); } throw new NotImplementedException(this.m_localizationService.GetString("error.type.NotImplementedException")); }
/// <summary> /// Creates a new operation context /// </summary> internal HL7OperationContext(Hl7MessageReceivedEventArgs eventInfo) { this.m_eventInfo = eventInfo; this.Uuid = Guid.NewGuid(); }
/// <summary> /// Handle the PIX merge request /// </summary> private IMessage HandlePixMerge(NHapi.Model.V231.Message.ADT_A39 request, Hl7MessageReceivedEventArgs evt) { // Get config var config = this.Context.GetService(typeof(ISystemConfigurationService)) as ISystemConfigurationService; var locale = this.Context.GetService(typeof(ILocalizationService)) as ILocalizationService; var dataService = this.Context.GetService(typeof(IClientRegistryDataService)) as IClientRegistryDataService; // Create a details array List <IResultDetail> dtls = new List <IResultDetail>(); // Validate the inbound message MessageUtil.Validate((IMessage)request, config, dtls, this.Context); IMessage response = null; // Control if (request == null) { return(null); } // Data controller //DataUtil dataUtil = new DataUtil() { Context = this.Context }; AuditUtil auditUtil = new AuditUtil() { Context = this.Context }; // Construct appropriate audit List <AuditData> audit = new List <AuditData>(); try { // Create Query Data ComponentUtility cu = new ComponentUtility() { Context = this.Context }; DeComponentUtility dcu = new DeComponentUtility() { Context = this.Context }; var data = cu.CreateComponents(request, dtls); if (data == null) { throw new InvalidOperationException(locale.GetString("MSGE00A")); } // Merge var result = dataService.Merge(data, request.MSH.ProcessingID.ProcessingID.Value == "P" ? DataPersistenceMode.Production : DataPersistenceMode.Debugging); if (result == null || result.VersionId == null) { throw new InvalidOperationException(locale.GetString("DTPE001")); } List <VersionedDomainIdentifier> deletedRecordIds = new List <VersionedDomainIdentifier>(), updatedRecordIds = new List <VersionedDomainIdentifier>(); // Subjects var oidData = config.OidRegistrar.GetOid("CR_CID").Oid; foreach (Person subj in data.FindAllComponents(SVC.Core.ComponentModel.HealthServiceRecordSiteRoleType.SubjectOf)) { PersonRegistrationRef replcd = subj.FindComponent(SVC.Core.ComponentModel.HealthServiceRecordSiteRoleType.ReplacementOf) as PersonRegistrationRef; deletedRecordIds.Add(new VersionedDomainIdentifier() { Identifier = replcd.Id.ToString(), Domain = oidData }); updatedRecordIds.Add(new VersionedDomainIdentifier() { Identifier = subj.Id.ToString(), Domain = oidData }); } // Now audit audit.Add(auditUtil.CreateAuditData("ITI-8", ActionType.Delete, OutcomeIndicator.Success, evt, deletedRecordIds)); audit.Add(auditUtil.CreateAuditData("ITI-8", ActionType.Update, OutcomeIndicator.Success, evt, updatedRecordIds)); // Now process the result response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(NHapi.Model.V231.Message.ACK)); MessageUtil.UpdateMSH(new NHapi.Base.Util.Terser(response), request, config); (response as NHapi.Model.V231.Message.ACK).MSH.MessageType.TriggerEvent.Value = request.MSH.MessageType.TriggerEvent.Value; (response as NHapi.Model.V231.Message.ACK).MSH.MessageType.MessageType.Value = "ACK"; } catch (Exception e) { Trace.TraceError(e.ToString()); if (!dtls.Exists(o => o.Message == e.Message || o.Exception == e)) { dtls.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); } response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(NHapi.Model.V231.Message.ACK)); audit.Add(auditUtil.CreateAuditData("ITI-8", ActionType.Delete, OutcomeIndicator.EpicFail, evt, new List <VersionedDomainIdentifier>())); } finally { IAuditorService auditSvc = this.Context.GetService(typeof(IAuditorService)) as IAuditorService; if (auditSvc != null) { foreach (var aud in audit) { auditSvc.SendAudit(aud); } } } return(response); }
/// <summary> /// Allows overridden classes to implement the message handling logic /// </summary> /// <param name="e">The message receive event args</param> /// <returns>The resulting message</returns> protected abstract IMessage HandleMessageInternal(Hl7MessageReceivedEventArgs e, Bundle parsed);
/// <summary> /// Authetnicate /// </summary> private IDisposable Authenticate(Hl7MessageReceivedEventArgs e) { IPrincipal principal = null; var msh = e.Message.GetStructure("MSH") as MSH; var sft = e.Message.GetStructure("SFT") as SFT; var sessionService = ApplicationServiceContext.Current.GetService <ISessionProviderService>(); if (string.IsNullOrEmpty(msh.Security.Value) && this.m_configuration.Security == Configuration.AuthenticationMethod.Msh8) { this.m_traceSource.TraceError("Must carry MSH-8 authorization token information"); throw new SecurityException(this.m_localizationService.GetString("error.messaging.h17.authorizationToken")); } if (msh.Security.Value?.StartsWith("sid://") == true) // Session identifier { var session = sessionService.Get(Enumerable.Range(5, msh.Security.Value.Length - 5) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(msh.Security.Value.Substring(x, 2), 16)) .ToArray()); principal = ApplicationServiceContext.Current.GetService <ISessionIdentityProviderService>().Authenticate(session) as IClaimsPrincipal; } else if (e is AuthenticatedHl7MessageReceivedEventArgs auth && auth.AuthorizationToken != null) { // Ensure proper authentication exists if (String.IsNullOrEmpty(msh.SendingApplication.NamespaceID.Value)) { this.m_traceSource.TraceError("MSH-3 must be provided for authenticating device/application"); throw new SecurityException(this.m_localizationService.FormatString("error.messaging.h17.authenticating", new { param = "MSH-3", param2 = " device/application" })); } else if (this.m_configuration.Security == Configuration.AuthenticationMethod.Sft4 && string.IsNullOrEmpty(sft.SoftwareBinaryID.Value)) { this.m_traceSource.TraceError("SFT-4 must be provided for authenticating application"); throw new SecurityException(this.m_localizationService.FormatString("error.messaging.h17.authenticating", new { param = "SFT-4", param2 = " application" })); } else if (this.m_configuration.Security == Configuration.AuthenticationMethod.Msh8 && string.IsNullOrEmpty(msh.Security.Value)) { this.m_traceSource.TraceError("MSH-8 must be provided for authenticating application"); throw new SecurityException(this.m_localizationService.FormatString("error.messaging.h17.authenticating", new { param = "MSH-8", param2 = " application" })); } String applicationId = msh.SendingApplication.NamespaceID.Value, applicationSecret = null; switch (this.m_configuration.Security) { case Configuration.AuthenticationMethod.None: // No special - authenticate the app using device creds applicationSecret = this.m_configuration.NoAuthenticationSecret; break; case Configuration.AuthenticationMethod.Msh8: applicationSecret = msh.Security.Value; break; case Configuration.AuthenticationMethod.Sft4: applicationSecret = sft.SoftwareBinaryID.Value; break; } IPrincipal certificatePrincipal = ApplicationServiceContext.Current.GetService <ICertificateIdentityProvider>()?.Authenticate(auth.AuthorizationToken); if (certificatePrincipal == null) { throw new InvalidOperationException("In order to use node authentication with X509 certificates - there must be a CertificateIdentityProvider configured"); } else if (certificatePrincipal.Identity is IApplicationIdentity) { principal = certificatePrincipal; } else { var applicationPrincipal = applicationSecret != null?ApplicationServiceContext.Current.GetService <IApplicationIdentityProviderService>()?.Authenticate(applicationId, applicationSecret) : null; if (applicationPrincipal == null && this.m_configuration.RequireAuthenticatedApplication) { this.m_traceSource.TraceError("Server requires authenticated application"); throw new UnauthorizedAccessException(this.m_localizationService.GetString("error.type.UnauthorizedAccessException")); } principal = new SanteDBClaimsPrincipal(new IIdentity[] { certificatePrincipal.Identity, applicationPrincipal?.Identity }.OfType <IClaimsIdentity>()); } }
/// <summary> /// Handle a PIX admission /// </summary> private IMessage HandlePixAdmit(NHapi.Model.V231.Message.ADT_A01 request, Hl7MessageReceivedEventArgs evt) { // Get config var config = this.Context.GetService(typeof(ISystemConfigurationService)) as ISystemConfigurationService; var locale = this.Context.GetService(typeof(ILocalizationService)) as ILocalizationService; var dataService = this.Context.GetService(typeof(IClientRegistryDataService)) as IClientRegistryDataService; // Create a details array List <IResultDetail> dtls = new List <IResultDetail>(); // Validate the inbound message MessageUtil.Validate((IMessage)request, config, dtls, this.Context); IMessage response = null; // Control if (request == null) { return(null); } // Data controller AuditUtil auditUtil = new AuditUtil() { Context = this.Context }; //DataUtil dataUtil = new DataUtil() { Context = this.Context }; // Construct appropriate audit AuditData audit = null; try { // Create Query Data ComponentUtility cu = new ComponentUtility() { Context = this.Context }; DeComponentUtility dcu = new DeComponentUtility() { Context = this.Context }; var data = cu.CreateComponents(request, dtls); if (data == null) { throw new InvalidOperationException(locale.GetString("MSGE00A")); } var result = dataService.Register(data, request.MSH.ProcessingID.ProcessingID.Value == "P" ? DataPersistenceMode.Production : DataPersistenceMode.Debugging); if (result == null || result.VersionId == null) { throw new InvalidOperationException(locale.GetString("DTPE001")); } dtls.AddRange(result.Details); audit = auditUtil.CreateAuditData("ITI-8", result.VersionId.UpdateMode == UpdateModeType.Update ? ActionType.Update : ActionType.Create, OutcomeIndicator.Success, evt, new List <VersionedDomainIdentifier>() { result.VersionId }); // Now process the result response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(NHapi.Model.V231.Message.ACK)); MessageUtil.UpdateMSH(new NHapi.Base.Util.Terser(response), request, config); (response as NHapi.Model.V231.Message.ACK).MSH.MessageType.TriggerEvent.Value = request.MSH.MessageType.TriggerEvent.Value; (response as NHapi.Model.V231.Message.ACK).MSH.MessageType.MessageType.Value = "ACK"; } catch (Exception e) { Trace.TraceError(e.ToString()); if (!dtls.Exists(o => o.Message == e.Message || o.Exception == e)) { dtls.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); } response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(NHapi.Model.V231.Message.ACK)); audit = auditUtil.CreateAuditData("ITI-8", ActionType.Create, OutcomeIndicator.EpicFail, evt, new List <VersionedDomainIdentifier>()); } finally { IAuditorService auditSvc = this.Context.GetService(typeof(IAuditorService)) as IAuditorService; if (auditSvc != null) { auditSvc.SendAudit(audit); } } return(response); }
/// <summary> /// Append query results to the message /// </summary> public virtual IMessage AppendQueryResult(IEnumerable results, Expression queryDefinition, IMessage currentResponse, Hl7MessageReceivedEventArgs evt, String matchConfiguration = null, int offset = 0) { var patients = results.OfType <Patient>(); if (patients.Count() == 0) { return(currentResponse); } var retVal = currentResponse as RSP_K23; var rqo = evt.Message as QBP_Q21; // Return domains List <AssigningAuthority> returnDomains = new List <AssigningAuthority>(); foreach (var rt in rqo.QPD.GetField(4).OfType <Varies>()) { var rid = new CX(rqo.Message); DeepCopy.copy(rt.Data as GenericComposite, rid); var domain = rid.AssigningAuthority.ToModel(); returnDomains.Add(domain); } var matchService = ApplicationServiceContext.Current.GetService <IRecordMatchingService>(); var matchConfigService = ApplicationServiceContext.Current.GetService <IRecordMatchingConfigurationService>(); // Process results int i = offset + 1; foreach (var itm in patients) { var queryInstance = retVal.QUERY_RESPONSE; // Expose PK? if (returnDomains.Count == 0 || returnDomains.Any(d => d.Key == this.m_configuration.LocalAuthority.Key)) { queryInstance.PID.GetPatientIdentifierList(queryInstance.PID.PatientIdentifierListRepetitionsUsed).FromModel(new EntityIdentifier(this.m_configuration.LocalAuthority, itm.Key.ToString())); queryInstance.PID.GetPatientIdentifierList(queryInstance.PID.PatientIdentifierListRepetitionsUsed - 1).IdentifierTypeCode.Value = "PI"; } foreach (var id in itm.LoadCollection <EntityIdentifier>("Identifiers")) { if (returnDomains.Count == 0 || returnDomains.Any(o => o.Key == id.AuthorityKey)) { queryInstance.PID.GetPatientIdentifierList(queryInstance.PID.PatientIdentifierListRepetitionsUsed).FromModel(id); } } if (returnDomains.Any(rid => rid.Key == this.m_configuration.LocalAuthority.Key)) { int idx = queryInstance.PID.PatientIdentifierListRepetitionsUsed; queryInstance.PID.GetPatientIdentifierList(idx).IDNumber.Value = itm.Key.Value.ToString(); queryInstance.PID.GetPatientIdentifierList(idx).AssigningAuthority.NamespaceID.Value = this.m_configuration.LocalAuthority.DomainName; queryInstance.PID.GetPatientIdentifierList(idx).AssigningAuthority.UniversalID.Value = this.m_configuration.LocalAuthority.Oid; queryInstance.PID.GetPatientIdentifierList(idx).AssigningAuthority.UniversalIDType.Value = "ISO"; } // No identifiers found in the response domains if (queryInstance.PID.PatientIdentifierListRepetitionsUsed > 0) { queryInstance.PID.SetIDPID.Value = (i++).ToString(); queryInstance.PID.GetPatientName(0).NameTypeCode.Value = "S"; } else { (currentResponse.GetStructure("QAK") as QAK).HitCount.Value = "0"; (currentResponse.GetStructure("QAK") as QAK).HitsRemaining.Value = "0"; (currentResponse.GetStructure("QAK") as QAK).ThisPayload.Value = "0"; (currentResponse.GetStructure("QAK") as QAK).QueryResponseStatus.Value = "NF"; } } return(retVal); }
/// <summary> /// Append query results to the message /// </summary> public virtual IMessage AppendQueryResult(IEnumerable results, Expression queryDefinition, IMessage currentResponse, Hl7MessageReceivedEventArgs evt, String matchConfiguration = null, int offset = 0) { var patients = results.OfType <Patient>(); if (patients.Count() == 0) { return(currentResponse); } var retVal = currentResponse as RSP_K21; var pidHandler = SegmentHandlers.GetSegmentHandler("PID"); var pd1Handler = SegmentHandlers.GetSegmentHandler("PD1"); var nokHandler = SegmentHandlers.GetSegmentHandler("NK1"); var matchService = ApplicationServiceContext.Current.GetService <IRecordMatchingService>(); var matchConfigService = ApplicationServiceContext.Current.GetService <IRecordMatchingConfigurationService>(); // Return domains var rqo = evt.Message as QBP_Q21; List <AssigningAuthority> returnDomains = new List <AssigningAuthority>(); foreach (var rt in rqo.QPD.GetField(8).OfType <Varies>()) { var rid = new CX(rqo.Message); DeepCopy.copy(rt.Data as GenericComposite, rid); var authority = rid.AssigningAuthority.ToModel(); returnDomains.Add(authority); } if (returnDomains.Count == 0) { returnDomains = null; } // Process results int i = offset + 1; foreach (var itm in patients) { var queryInstance = retVal.GetQUERY_RESPONSE(retVal.QUERY_RESPONSERepetitionsUsed); pidHandler.Create(itm, queryInstance, returnDomains?.ToArray()); pd1Handler.Create(itm, queryInstance, null); nokHandler.Create(itm, queryInstance, null); queryInstance.PID.SetIDPID.Value = (i++).ToString(); // QRI? if (matchService != null && !String.IsNullOrEmpty(matchConfiguration)) { var score = matchService.Score <Patient>(itm, queryDefinition as Expression <Func <Patient, bool> >, matchConfiguration); queryInstance.QRI.CandidateConfidence.Value = score.Score.ToString(); queryInstance.QRI.AlgorithmDescriptor.Identifier.Value = matchConfiguration; } } return(retVal); }
/// <summary> /// Transport has received a message! /// </summary> private void m_transport_MessageReceived(object sender, Hl7MessageReceivedEventArgs e) { IMessagePersistenceService messagePersister = ApplicationServiceContext.Current.GetService(typeof(IMessagePersistenceService)) as IMessagePersistenceService; // Find the message that supports the type Terser msgTerser = new Terser(e.Message); string messageType = String.Format("{0}^{1}", msgTerser.Get("/MSH-9-1"), msgTerser.Get("/MSH-9-2")); string messageId = msgTerser.Get("/MSH-10"); // Find a handler HandlerDefinition handler = m_serviceDefinition.MessageHandlers.Find(o => o.Types.Exists(a => a.Name == messageType)), defaultHandler = m_serviceDefinition.MessageHandlers.Find(o => o.Types.Exists(a => a.Name == "*")); if (handler != null && handler.Types.Find(o => o.Name == messageType).IsQuery || defaultHandler != null && defaultHandler.Types.Find(o => o.Name == "*").IsQuery) { messagePersister = null; } // Have we already processed this message? MessageState msgState = MessageState.New; if (messagePersister != null) { msgState = messagePersister.GetMessageState(messageId); } switch (msgState) { case MessageState.New: if (messagePersister != null) { messagePersister.PersistMessage(messageId, CreateMessageStream(e.Message)); } if (handler == null && defaultHandler == null) { throw new InvalidOperationException(String.Format("Cannot find message handler for '{0}'", messageType)); } e.Response = (handler ?? defaultHandler).Handler.HandleMessage(e); if (e.Response == null) { throw new InvalidOperationException("Couldn't process message"); } msgTerser = new Terser(e.Response); if (messagePersister != null) { messagePersister.PersistResultMessage(msgTerser.Get("/MSH-10"), messageId, CreateMessageStream(e.Response)); } break; case MessageState.Active: throw new InvalidOperationException("Message already in progress"); case MessageState.Complete: NHapi.Base.Parser.PipeParser pp = new NHapi.Base.Parser.PipeParser(); using (var rdr = new StreamReader(messagePersister.GetMessageResponseMessage(messageId))) e.Response = pp.Parse(rdr.ReadToEnd()); break; } }
/// <summary> /// Create audit data /// </summary> internal AuditData CreateAuditData(string itiName, ActionType action, OutcomeIndicator outcome, Hl7MessageReceivedEventArgs msgEvent, List <VersionedDomainIdentifier> identifiers) { // Audit data AuditData retVal = null; AuditableObjectLifecycle lifecycle = AuditableObjectLifecycle.Access; // Get the config service ISystemConfigurationService config = Context.GetService(typeof(ISystemConfigurationService)) as ISystemConfigurationService; Terser terser = new Terser(msgEvent.Message); // Source and dest string sourceData = String.Format("{0}|{1}", terser.Get("/MSH-3"), terser.Get("/MSH-4")), destData = String.Format("{0}|{1}", terser.Get("/MSH-5"), terser.Get("/MSH-6")); switch (itiName) { case "ITI-21": { retVal = new AuditData(DateTime.Now, action, outcome, EventIdentifierType.Query, new CodeValue(itiName, "IHE Transactions") { DisplayName = "Patient Demographics Query" }); // Audit actor for Patient Identity Source retVal.Actors.Add(new AuditActorData() { UserIsRequestor = true, UserIdentifier = sourceData, ActorRoleCode = new List <CodeValue>() { new CodeValue("110153", "DCM") { DisplayName = "Source" } }, NetworkAccessPointId = msgEvent.SolicitorEndpoint.Host, NetworkAccessPointType = msgEvent.SolicitorEndpoint.HostNameType == UriHostNameType.Dns ? NetworkAccessPointType.MachineName : NetworkAccessPointType.IPAddress }); // Add query parameters retVal.AuditableObjects.Add( new AuditableObject() { IDTypeCode = AuditableObjectIdType.Custom, CustomIdTypeCode = new CodeValue(itiName, "IHE Transactions") { DisplayName = "Patient Demographics Query" }, QueryData = Convert.ToBase64String(CreateMessageSerialized(msgEvent.Message)), Type = AuditableObjectType.SystemObject, Role = AuditableObjectRole.Query, ObjectId = terser.Get("/QPD-2"), ObjectData = new Dictionary <string, byte[]>() { { "MSH-10", System.Text.Encoding.ASCII.GetBytes(terser.Get("/MSH-10")) } } } ); // Audit actor for PDQ retVal.Actors.Add(new AuditActorData() { UserIdentifier = destData, UserIsRequestor = false, ActorRoleCode = new List <CodeValue>() { new CodeValue("110152", "DCM") { DisplayName = "Destination" } }, NetworkAccessPointType = NetworkAccessPointType.MachineName, NetworkAccessPointId = Dns.GetHostName(), AlternativeUserId = Process.GetCurrentProcess().Id.ToString() }); break; } case "ITI-8": { retVal = new AuditData(DateTime.Now, action, outcome, EventIdentifierType.PatientRecord, new CodeValue(itiName, "IHE Transactions") { DisplayName = "Patient Identity Feed" }); // Audit actor for Patient Identity Source retVal.Actors.Add(new AuditActorData() { UserIsRequestor = true, UserIdentifier = sourceData, ActorRoleCode = new List <CodeValue>() { new CodeValue("110153", "DCM") { DisplayName = "Source" } }, NetworkAccessPointId = msgEvent.SolicitorEndpoint.Host, NetworkAccessPointType = msgEvent.SolicitorEndpoint.HostNameType == UriHostNameType.Dns ? NetworkAccessPointType.MachineName : NetworkAccessPointType.IPAddress }); // Audit actor for PDQ retVal.Actors.Add(new AuditActorData() { UserIdentifier = destData, UserIsRequestor = false, ActorRoleCode = new List <CodeValue>() { new CodeValue("110152", "DCM") { DisplayName = "Destination" } }, NetworkAccessPointType = NetworkAccessPointType.MachineName, NetworkAccessPointId = Dns.GetHostName(), AlternativeUserId = Process.GetCurrentProcess().Id.ToString() }); break; } case "ITI-9": { retVal = new AuditData(DateTime.Now, action, outcome, EventIdentifierType.Query, new CodeValue(itiName, "IHE Transactions") { DisplayName = "PIX Query" }); // Audit actor for Patient Identity Source retVal.Actors.Add(new AuditActorData() { UserIsRequestor = true, UserIdentifier = sourceData, ActorRoleCode = new List <CodeValue>() { new CodeValue("110153", "DCM") { DisplayName = "Source" } }, NetworkAccessPointId = msgEvent.SolicitorEndpoint.Host, NetworkAccessPointType = msgEvent.SolicitorEndpoint.HostNameType == UriHostNameType.Dns ? NetworkAccessPointType.MachineName : NetworkAccessPointType.IPAddress }); // Add query parameters retVal.AuditableObjects.Add( new AuditableObject() { IDTypeCode = AuditableObjectIdType.Custom, CustomIdTypeCode = new CodeValue("ITI-9", "IHE Transactions") { DisplayName = "PIX Query" }, QueryData = Convert.ToBase64String(CreateMessageSerialized(msgEvent.Message)), Type = AuditableObjectType.SystemObject, Role = AuditableObjectRole.Query, ObjectId = terser.Get("/QPD-2"), ObjectData = new Dictionary <string, byte[]>() { { "MSH-10", System.Text.Encoding.ASCII.GetBytes(terser.Get("/MSH-10")) } } } ); // Audit actor for PDQ retVal.Actors.Add(new AuditActorData() { UserIdentifier = destData, UserIsRequestor = false, ActorRoleCode = new List <CodeValue>() { new CodeValue("110152", "DCM") { DisplayName = "Destination" } }, NetworkAccessPointType = NetworkAccessPointType.MachineName, NetworkAccessPointId = Dns.GetHostName(), AlternativeUserId = Process.GetCurrentProcess().Id.ToString() }); break; } } var expDatOid = config.OidRegistrar.GetOid("CR_CID"); // HACK: Use only patient identifiers in the output foreach (var id in identifiers.Where(o => o.Domain != expDatOid.Oid).ToArray()) { RegistrationEvent evt = this.m_dataPersistence.GetContainer(id, true) as RegistrationEvent; if (evt != null) { identifiers.Remove(id); foreach (Person subj in evt.FindAllComponents(HealthServiceRecordSiteRoleType.SubjectOf)) { identifiers.Add(new VersionedDomainIdentifier() { Identifier = subj.Id.ToString(), Domain = expDatOid.Oid }); } } } // Audit patients foreach (var id in identifiers) { // If the id is not a patient then // Construct the audit object AuditableObject aud = new AuditableObject() { IDTypeCode = AuditableObjectIdType.PatientNumber, Role = AuditableObjectRole.Patient, Type = AuditableObjectType.Person }; // Lifecycle switch (action) { case ActionType.Create: aud.LifecycleType = AuditableObjectLifecycle.Creation; break; case ActionType.Delete: aud.LifecycleType = AuditableObjectLifecycle.LogicalDeletion; break; case ActionType.Execute: aud.LifecycleType = AuditableObjectLifecycle.Access; break; case ActionType.Read: aud.LifecycleType = AuditableObjectLifecycle.Disclosure; break; case ActionType.Update: aud.LifecycleType = AuditableObjectLifecycle.Amendment; break; } aud.ObjectData.Add("MSH-10", System.Text.Encoding.ASCII.GetBytes(terser.Get("/MSH-10"))); aud.ObjectId = String.Format("{1}^^^{2}&{0}&ISO", expDatOid.Oid, id.Identifier, expDatOid.Attributes.Find(o => o.Key == "AssigningAuthorityName").Value); retVal.AuditableObjects.Add(aud); } return(retVal); }
/// <summary> /// Create audit data /// </summary> public AuditData CreateAuditData(string itiName, ActionType action, OutcomeIndicator outcome, Hl7MessageReceivedEventArgs msgEvent, RegistryQueryResult result) { // Create the call to the other create audit data message by constructing the list of disclosed identifiers List <VersionedDomainIdentifier> vids = new List <VersionedDomainIdentifier>(result.Results.Count); foreach (var res in result.Results) { if (res == null) { continue; } var subj = res.FindComponent(SVC.Core.ComponentModel.HealthServiceRecordSiteRoleType.SubjectOf) as Person; if (subj == null) { continue; } vids.Add(new VersionedDomainIdentifier() { Domain = this.m_configService.OidRegistrar.GetOid("CR_CID").Oid, Identifier = subj.Id.ToString() }); } return(CreateAuditData(itiName, action, outcome, msgEvent, vids)); }
/// <summary> /// Handle a PIX query /// </summary> private IMessage HandlePixQuery(NHapi.Model.V25.Message.QBP_Q21 request, Hl7MessageReceivedEventArgs evt) { // Get config var config = this.Context.GetService(typeof(ISystemConfigurationService)) as ISystemConfigurationService; var locale = this.Context.GetService(typeof(ILocalizationService)) as ILocalizationService; var dataService = this.Context.GetService(typeof(IClientRegistryDataService)) as IClientRegistryDataService; // Create a details array List <IResultDetail> dtls = new List <IResultDetail>(); // Validate the inbound message MessageUtil.Validate((IMessage)request, config, dtls, this.Context); IMessage response = null; // Control if (request == null) { return(null); } // Construct appropriate audit AuditData audit = null; // Data controller AuditUtil auditUtil = new AuditUtil() { Context = this.Context }; //DataUtil dataUtil = new DataUtil() { Context = this.Context }; try { // Create Query Data ComponentUtility cu = new ComponentUtility() { Context = this.Context }; DeComponentUtility dcu = new DeComponentUtility() { Context = this.Context }; var data = cu.CreateQueryComponents(request, dtls); if (data == null) { throw new InvalidOperationException(locale.GetString("MSGE00A")); } RegistryQueryResult result = dataService.Query(data); dtls.AddRange(result.Details); // Update locations? foreach (var dtl in dtls) { if (dtl is PatientNotFoundResultDetail) { dtl.Location = "QPD^1^3^1^1"; } else if (dtl is UnrecognizedPatientDomainResultDetail) { dtl.Location = "QPD^1^3^1^4"; } else if (dtl is UnrecognizedTargetDomainResultDetail) { dtl.Location = "QPD^1^4^"; } } audit = auditUtil.CreateAuditData("ITI-9", ActionType.Execute, OutcomeIndicator.Success, evt, result); // Now process the result response = dcu.CreateRSP_K23(result, dtls); //var r = dcu.CreateRSP_K23(null, null); // Copy QPD try { (response as NHapi.Model.V25.Message.RSP_K23).QPD.MessageQueryName.Identifier.Value = request.QPD.MessageQueryName.Identifier.Value; Terser reqTerser = new Terser(request), rspTerser = new Terser(response); rspTerser.Set("/QPD-1", reqTerser.Get("/QPD-1")); rspTerser.Set("/QPD-2", reqTerser.Get("/QPD-2")); rspTerser.Set("/QPD-3-1", reqTerser.Get("/QPD-3-1")); rspTerser.Set("/QPD-3-4-1", reqTerser.Get("/QPD-3-4-1")); rspTerser.Set("/QPD-3-4-2", reqTerser.Get("/QPD-3-4-2")); rspTerser.Set("/QPD-3-4-3", reqTerser.Get("/QPD-3-4-3")); rspTerser.Set("/QPD-4-1", reqTerser.Get("/QPD-4-1")); rspTerser.Set("/QPD-4-4-1", reqTerser.Get("/QPD-4-4-1")); rspTerser.Set("/QPD-4-4-2", reqTerser.Get("/QPD-4-4-2")); rspTerser.Set("/QPD-4-4-3", reqTerser.Get("/QPD-4-4-3")); } catch (Exception e) { Trace.TraceError(e.ToString()); } //MessageUtil.((response as NHapi.Model.V25.Message.RSP_K23).QPD, request.QPD); MessageUtil.UpdateMSH(new NHapi.Base.Util.Terser(response), request, config); } catch (Exception e) { Trace.TraceError(e.ToString()); if (!dtls.Exists(o => o is UnrecognizedPatientDomainResultDetail || o is UnrecognizedTargetDomainResultDetail || o.Message == e.Message || o.Exception == e)) { dtls.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); } response = MessageUtil.CreateNack(request, dtls, this.Context, typeof(RSP_K23)); Terser errTerser = new Terser(response); // HACK: Fix the generic ACK with a real ACK for this message errTerser.Set("/MSH-9-2", "K23"); errTerser.Set("/MSH-9-3", "RSP_K23"); errTerser.Set("/QAK-2", "AE"); errTerser.Set("/MSA-1", "AE"); errTerser.Set("/QAK-1", request.QPD.QueryTag.Value); audit = auditUtil.CreateAuditData("ITI-9", ActionType.Execute, OutcomeIndicator.EpicFail, evt, new List <VersionedDomainIdentifier>()); } finally { IAuditorService auditSvc = this.Context.GetService(typeof(IAuditorService)) as IAuditorService; if (auditSvc != null) { auditSvc.SendAudit(audit); } } return(response); }
/// <summary> /// Performs a merge of the specified patient /// </summary> protected virtual IMessage PerformMerge(Hl7MessageReceivedEventArgs e, Bundle b) { return(null); }
/// <summary> /// Handle message internally /// </summary> protected override IMessage HandleMessageInternal(Hl7MessageReceivedEventArgs e, Bundle parsed) { // First we want to get the map var msh = e.Message.GetStructure("MSH") as MSH; var trigger = msh.MessageType.TriggerEvent.Value; var map = this.GetMapping(trigger); var qpd = e.Message.GetStructure("QPD") as QPD; try { if (map.ResponseType == null) { throw new NotSupportedException($"Response type not found"); } // First, process the query parameters var query = map.QueryHandler.ParseQuery(qpd, map); if (query.Count == 0) { throw new InvalidOperationException("Query must provide at least one understood filter"); } // Control? var rcp = e.Message.GetStructure("RCP") as RCP; int? count = null, offset = 0; Guid queryId = Guid.NewGuid(); if (!String.IsNullOrEmpty(rcp.QuantityLimitedRequest.Quantity.Value)) { count = Int32.Parse(rcp.QuantityLimitedRequest.Quantity.Value); } // Continuation? var dsc = e.Message.GetStructure("DSC") as DSC; if (!String.IsNullOrEmpty(dsc.ContinuationPointer.Value)) { if (!Guid.TryParse(dsc.ContinuationPointer.Value, out queryId)) { throw new InvalidOperationException($"DSC^1 must be UUID provided by this service."); } } // Get the query tag which is the current offset if (ApplicationServiceContext.Current.GetService <Core.Services.IQueryPersistenceService>()?.IsRegistered(queryId) == true) { var tag = ApplicationServiceContext.Current.GetService <Core.Services.IQueryPersistenceService>().GetQueryTag(queryId); if (tag is int) { offset = (int)tag; } } // Next, we want to get the repository for the bound type var repoService = ApplicationServiceContext.Current.GetService(typeof(IRepositoryService <>).MakeGenericType(map.QueryTarget)); if (repoService == null) { throw new InvalidOperationException($"Cannot find repository service for {map.QueryTargetXml}"); } // Build query int totalResults = 0; IEnumerable results = null; Expression filterQuery = null; if (query.ContainsKey("_id")) { Guid id = Guid.Parse(query["_id"][0]); object result = repoService.GetType().GetMethod("Get", new Type[] { typeof(Guid) }).Invoke(repoService, new object[] { id }); results = new List <IdentifiedData>(); if (result != null) { (results as IList).Add(result); totalResults = 1; } } else { var queryMethod = typeof(QueryExpressionParser).GetGenericMethod(nameof(QueryExpressionParser.BuildLinqExpression), new Type[] { map.QueryTarget }, new Type[] { typeof(NameValueCollection) }); filterQuery = queryMethod.Invoke(null, new object[] { query }) as Expression; // Now we want to query object[] parameters = { filterQuery, offset.Value, (int?)count ?? 100, null, queryId, null }; var findMethod = repoService.GetType().GetMethod("Find", new Type[] { filterQuery.GetType(), typeof(int), typeof(int?), typeof(int).MakeByRefType(), typeof(Guid), typeof(ModelSort <>).MakeGenericType(map.QueryTarget).MakeArrayType() }); results = findMethod.Invoke(repoService, parameters) as IEnumerable; totalResults = (int)parameters[3]; } // Save the tag if (dsc.ContinuationPointer.Value != queryId.ToString() && offset.Value + count.GetValueOrDefault() < totalResults) { ApplicationServiceContext.Current.GetService <Core.Services.IQueryPersistenceService>()?.SetQueryTag(queryId, count); } AuditUtil.AuditQuery(Core.Auditing.OutcomeIndicator.Success, PipeParser.Encode(qpd, new EncodingCharacters('|', "^~\\&")), results.OfType <IdentifiedData>().ToArray()); // Query basics return(this.CreateQueryResponse(e, filterQuery, map, results, queryId, offset.GetValueOrDefault(), count ?? 100, totalResults)); } catch (Exception ex) { this.m_traceSource.TraceEvent(EventLevel.Error, "Error executing query: {0}", ex); AuditUtil.AuditQuery <IdentifiedData>(Core.Auditing.OutcomeIndicator.MinorFail, PipeParser.Encode(qpd, new EncodingCharacters('|', "^~\\&"))); // Now we construct the response return(this.CreateNACK(map.ResponseType, e.Message, ex, e)); } }
/// <summary> /// Append query results to the message /// </summary> public virtual IMessage AppendQueryResult(IEnumerable results, Expression queryDefinition, IMessage currentResponse, Hl7MessageReceivedEventArgs evt, int offset = 0) { var patients = results.OfType <Patient>(); if (patients.Count() == 0) { return(currentResponse); } var retVal = currentResponse as RSP_K21; var pidHandler = SegmentHandlers.GetSegmentHandler("PID"); var pd1Handler = SegmentHandlers.GetSegmentHandler("PD1"); var nokHandler = SegmentHandlers.GetSegmentHandler("NK1"); var matchService = ApplicationServiceContext.Current.GetService <IRecordMatchingService>(); var matchConfigService = ApplicationServiceContext.Current.GetService <IRecordMatchingConfigurationService>(); // Return domains var rqo = evt.Message as QBP_Q21; List <AssigningAuthority> returnDomains = new List <AssigningAuthority>(); foreach (var rt in rqo.QPD.GetField(8).OfType <Varies>()) { var rid = new CX(rqo.Message); DeepCopy.Copy(rt.Data as GenericComposite, rid); var authority = rid.AssigningAuthority.ToModel(); returnDomains.Add(authority); } if (returnDomains.Count == 0) { returnDomains = null; } // Process results int i = offset + 1; IEnumerable <dynamic> resultScores = patients.Select(o => new { Patient = o, WasScored = false }); if (this.m_scoringService != null) { resultScores = this.m_scoringService.Score <Patient>(queryDefinition as Expression <Func <Patient, bool> >, patients).Select(o => new { Patient = o.Result, Score = o.Score, Method = o.Method, WasScored = true }); } foreach (var itm in resultScores) { var queryInstance = retVal.GetQUERY_RESPONSE(retVal.QUERY_RESPONSERepetitionsUsed); pidHandler.Create(itm.Patient, queryInstance, returnDomains?.ToArray()); pd1Handler.Create(itm.Patient, queryInstance, null); nokHandler.Create(itm.Patient, queryInstance, null); queryInstance.PID.SetIDPID.Value = (i++).ToString(); if (itm.WasScored) { queryInstance.QRI.CandidateConfidence.Value = itm.Score.ToString(); switch ((RecordMatchMethod)itm.Method) { case RecordMatchMethod.Identifier: queryInstance.QRI.GetMatchReasonCode(0).Value = "SS"; break; case RecordMatchMethod.Simple: queryInstance.QRI.GetMatchReasonCode(0).Value = "NA"; break; case RecordMatchMethod.Weighted: queryInstance.QRI.GetMatchReasonCode(0).Value = "NP"; break; } queryInstance.QRI.AlgorithmDescriptor.Identifier.Value = this.m_scoringService.ServiceName; } else { queryInstance.QRI.CandidateConfidence.Value = "1.0"; queryInstance.QRI.AlgorithmDescriptor.Identifier.Value = "PTNM"; } } return(retVal); }
/// <summary> /// Creates the negative ack /// </summary> /// <remarks>This overridden method allows for capturing of errors</remarks> protected override IMessage CreateNACK(Type nackType, IMessage request, Exception error, Hl7MessageReceivedEventArgs receiveData) { var retVal = base.CreateNACK(nackType, request, error, receiveData); var ex = error; while (ex != null) { if (ex is KeyNotFoundException) { (retVal.GetStructure("MSA") as MSA).AcknowledgmentCode.Value = "AR"; break; } ex = ex.InnerException; } return(retVal); }