/// <summary> /// Provide a fault /// </summary> public bool ProvideFault(Exception error, RestResponseMessage response) { this.m_tracer.TraceEvent(EventLevel.Error, "Error on WCF FHIR Pipeline: {0}", error); RestOperationContext.Current.OutgoingResponse.StatusCode = ClassifyErrorCode(error); if (RestOperationContext.Current.OutgoingResponse.StatusCode == 401) { // Get to the root of the error while (error.InnerException != null) { error = error.InnerException; } if (error is PolicyViolationException pve) { var method = RestOperationContext.Current.AppliedPolicies.Any(o => o.GetType().Name.Contains("Basic")) ? "Basic" : "Bearer"; response.AddAuthenticateHeader(method, RestOperationContext.Current.IncomingRequest.Url.Host, "insufficient_scope", pve.PolicyId, error.Message); } else if (error is SecurityTokenException ste) { response.AddAuthenticateHeader("Bearer", RestOperationContext.Current.IncomingRequest.Url.Host, "token_error", description: ste.Message); } else if (error is SecuritySessionException ses) { switch (ses.Type) { case SessionExceptionType.Expired: case SessionExceptionType.NotYetValid: case SessionExceptionType.NotEstablished: response.StatusCode = (int)System.Net.HttpStatusCode.Unauthorized; response.AddAuthenticateHeader("Bearer", RestOperationContext.Current.IncomingRequest.Url.Host, "unauthorized", PermissionPolicyIdentifiers.Login, ses.Message); break; default: response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden; break; } } } var errorResult = DataTypeConverter.CreateErrorResult(error); // Return error in XML only at this point new FhirMessageDispatchFormatter().SerializeResponse(response, null, errorResult); return(true); }
/// <summary> /// Invoke the process message operation /// </summary> public Resource Invoke(Parameters parameters) { // Extract the parameters var contentParameter = parameters.Parameter.Find(o => o.Name == "content")?.Resource as Bundle; var asyncParameter = parameters.Parameter.Find(o => o.Name == "async")?.Value as FhirBoolean; if (contentParameter == null) { this.m_tracer.TraceError("Missing content parameter"); throw new ArgumentNullException(m_localizationService.GetString("error.type.ArgumentNullException")); } else if (asyncParameter?.Value.GetValueOrDefault() == true) { this.m_tracer.TraceError("Asynchronous messaging is not supported by this repository"); throw new InvalidOperationException(m_localizationService.GetString("error.type.NotSupportedException")); } // Message must have a message header var messageHeader = contentParameter.Entry.Find(o => o.Resource.TryDeriveResourceType(out ResourceType rt) && rt == ResourceType.MessageHeader)?.Resource as MessageHeader; if (messageHeader == null) { this.m_tracer.TraceError("Message bundle does not contain a MessageHeader"); throw new ArgumentException(m_localizationService.GetString("error.type.ArgumentNullException")); } // Determine the appropriate action handler if (messageHeader.Event is FhirUri eventUri) { var handler = ExtensionUtil.GetMessageOperationHandler(new Uri(eventUri.Value)); if (handler == null) { this.m_tracer.TraceError($"There is no message handler for event {eventUri}"); throw new NotSupportedException(m_localizationService.GetString("error.type.NotSupportedException")); } var retVal = new Bundle(); // Return for operation retVal.Meta = new Meta() { LastUpdated = DateTimeOffset.Now }; var uuid = Guid.NewGuid(); try { // HACK: The .EndsWith is a total hack - FHIR wants .FullUrl to be absolute, but many senders will send relative references which var opReturn = handler.Invoke(messageHeader, contentParameter.Entry.Where(o => messageHeader.Focus.Any(f => o.FullUrl == f.Reference || $"{o.Resource.TypeName}/{o.Resource.Id}" == f.Reference)).ToArray()); retVal.Entry.Add(new Bundle.EntryComponent() { FullUrl = $"urn:uuid:{uuid}", Resource = new MessageHeader() { Id = uuid.ToString(), Response = new MessageHeader.ResponseComponent() { Code = MessageHeader.ResponseType.Ok, Details = new ResourceReference($"urn:uuid:{opReturn.Id}") } } }); // HACK: Another hack - FullUrl is assumed to be a UUID because I'm not turning an id of XXX and trying to derive a fullUrl for something that is in a // bundle anyways retVal.Entry.Add(new Bundle.EntryComponent() { FullUrl = $"urn:uuid:{opReturn.Id}", Resource = opReturn }); } catch (Exception e) { var outcome = DataTypeConverter.CreateErrorResult(e); outcome.Id = Guid.NewGuid().ToString(); retVal.Entry.Add(new Bundle.EntryComponent() { FullUrl = $"urn:uuid:{uuid}", Resource = new MessageHeader() { Id = uuid.ToString(), Response = new MessageHeader.ResponseComponent() { Code = MessageHeader.ResponseType.FatalError, Details = new ResourceReference($"urn:uuid:{outcome.Id}") } } }); retVal.Entry.Add(new Bundle.EntryComponent() { FullUrl = $"urn:uuid:{outcome.Id}", Resource = outcome }); throw new FhirException((System.Net.HttpStatusCode)FhirErrorEndpointBehavior.ClassifyErrorCode(e), retVal, m_localizationService.GetString("error.type.FhirException"), e); } finally { retVal.Timestamp = DateTime.Now; retVal.Type = Bundle.BundleType.Message; } return(retVal); } else { this.m_tracer.TraceError("Currently message headers with EventCoding are not supported"); throw new InvalidOperationException(m_localizationService.GetString("error.type.NotSupportedException.userMessage")); } }
/// <summary> /// Install dataset using FHIR services /// </summary> public void InstallDataset(object sender, EventArgs e) { using (AuthenticationContext.EnterSystemContext()) { // Data directory var dataDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "data", "fhir"); var fhirXmlParser = new FhirXmlParser(); var fhirJsonParser = new FhirJsonParser(); if (!Directory.Exists(dataDirectory)) { Directory.CreateDirectory(dataDirectory); } this.m_traceSource.TraceInfo("Scanning Directory {0} for FHIR objects", dataDirectory); // Process foreach (var f in Directory.GetFiles(dataDirectory, "*.*")) { try { Resource fhirResource = null; switch (Path.GetExtension(f).ToLowerInvariant()) { case ".json": using (var fs = File.OpenRead(f)) using (var tr = new StreamReader(fs)) using (var jr = new JsonTextReader(tr)) { fhirResource = fhirJsonParser.Parse(jr) as Resource; } break; case ".xml": using (var fs = File.OpenRead(f)) using (var xr = XmlReader.Create(fs)) { fhirResource = fhirXmlParser.Parse(xr) as Resource; } break; case ".completed": case ".response": continue; // skip file } // No FHIR resource if (fhirResource == null) { throw new InvalidOperationException($"Could not parse a FHIR resource from {f}"); } // Process the resource if (!fhirResource.TryDeriveResourceType(out var rt)) { throw new InvalidOperationException($"FHIR API doesn't support {fhirResource.TypeName}"); } var handler = FhirResourceHandlerUtil.GetResourceHandler(rt); if (handler == null) { throw new InvalidOperationException($"This instance of SanteMPI does not support {rt}"); } // Handle the resource Resource fhirResult = null; try { fhirResult = handler.Create(fhirResource, TransactionMode.Commit); File.Move(f, Path.ChangeExtension(f, "completed")); // Move to completed so it is not processed again. } catch (Exception ex) { this.m_traceSource.TraceError("Error Applying Dataset: {0}", ex); fhirResult = DataTypeConverter.CreateErrorResult(ex); } // Write the response using (var fs = File.Create(Path.ChangeExtension(f, "response"))) { switch (Path.GetExtension(f).ToLowerInvariant()) { case ".json": using (var tw = new StreamWriter(fs)) using (var jw = new JsonTextWriter(tw)) { new FhirJsonSerializer().Serialize(fhirResult, jw); } break; case ".xml": using (var xw = XmlWriter.Create(fs)) { new FhirXmlSerializer().Serialize(fhirResult, xw); } break; } } } catch (Exception ex) { this.m_traceSource.TraceError("Error applying {0}: {1}", f, ex); throw; } } } }