public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log) { string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); var resp = await FHIRUtils.CallFHIRServer("", FHIRUtils.TransformBundle(requestBody, log), HttpMethod.Post, log); string r = "{}"; if (resp != null) { r = resp.Content; } int sc = (int)resp.Status; return(new ContentResult() { Content = r, StatusCode = sc, ContentType = "application/json" }); }
public static string TransformBundle(string requestBody, ILogger log) { JObject result = JObject.Parse(requestBody); if (result == null || result["resourceType"] == null || result["type"] == null) { return(requestBody); } string rtt = result.FHIRResourceType(); string bt = (string)result["type"]; if (rtt.Equals("Bundle") && bt.Equals("transaction")) { log.LogInformation($"TransformBundleProcess: looks like a valid transaction bundle"); JArray entries = (JArray)result["entry"]; if (entries.IsNullOrEmpty()) { return(result.ToString()); } log.LogInformation($"TransformBundleProcess: Phase 1 searching for existing entries on FHIR Server..."); foreach (JToken tok in entries) { if (!tok.IsNullOrEmpty() && tok["request"]["ifNoneExist"] != null) { string resource = (string)tok["request"]["url"]; string query = (string)tok["request"]["ifNoneExist"]; log.LogInformation($"TransformBundleProcess:Loading Resource {resource} with query {query}"); var r = FHIRUtils.CallFHIRServer($"{resource}?{query}", "", HttpMethod.Get, log).Result; if (r.Success && r.Content != null) { var rs = JObject.Parse(r.Content); if (!rs.IsNullOrEmpty() && ((string)rs["resourceType"]).Equals("Bundle") && !rs["entry"].IsNullOrEmpty()) { JArray respentries = (JArray)rs["entry"]; string existingid = "urn:uuid:" + (string)respentries[0]["resource"]["id"]; tok["fullUrl"] = existingid; } } } } //reparse JSON with replacement of existing ids prepare to convert to Batch bundle with PUT to maintain relationships Dictionary <string, string> convert = new Dictionary <string, string>(); result["type"] = "batch"; entries = (JArray)result["entry"]; foreach (JToken tok in entries) { string urn = (string)tok["fullUrl"]; if (!string.IsNullOrEmpty(urn) && !tok["resource"].IsNullOrEmpty()) { string rt = (string)tok["resource"]["resourceType"]; string rid = urn.Replace("urn:uuid:", ""); tok["resource"]["id"] = rid; if (!convert.TryAdd(rid, rt)) { //Duplicate catch Guid g = Guid.NewGuid(); rid = g.ToString(); tok["resource"]["id"] = rid; } tok["request"]["method"] = "PUT"; tok["request"]["url"] = $"{rt}?_id={rid}"; } } log.LogInformation($"TransformBundleProcess: Phase 2 Localizing {convert.Count} resource entries..."); IEnumerable <JToken> refs = result.SelectTokens("$..reference"); foreach (JToken item in refs) { string s = item.ToString(); string t = ""; s = s.Replace("urn:uuid:", ""); if (convert.TryGetValue(s, out t)) { item.Replace(t + "/" + s); } } log.LogInformation($"TransformBundleProcess: Complete."); return(result.ToString()); } return(requestBody); }
public static async Task Run([EventGridTrigger] EventGridEvent blobCreatedEvent, [Blob("{data.url}", FileAccess.Read, Connection = "FBI-STORAGEACCT")] Stream myBlob, ILogger log) { bool trbundles = true; string strbundles = System.Environment.GetEnvironmentVariable("FBI-TRANSFORMBUNDLES"); if (!string.IsNullOrEmpty(strbundles) && (strbundles.ToLower().Equals("no") || strbundles.ToLower().Equals("false"))) { trbundles = false; } StorageBlobCreatedEventData createdEvent = ((JObject)blobCreatedEvent.Data).ToObject <StorageBlobCreatedEventData>(); string name = createdEvent.Url.Substring(createdEvent.Url.LastIndexOf('/') + 1); if (myBlob == null) { return; } log.LogInformation($"ImportFHIRBUndles: Processing file Name:{name} \n Size: {myBlob.Length}"); var cbclient = StorageUtils.GetCloudBlobClient(System.Environment.GetEnvironmentVariable("FBI-STORAGEACCT")); StreamReader reader = new StreamReader(myBlob); var text = await reader.ReadToEndAsync(); var trtext = (trbundles ? FHIRUtils.TransformBundle(text, log) : text); var fhirbundle = await FHIRUtils.CallFHIRServer("", trtext, HttpMethod.Post, log); var result = LoadErrorsDetected(trtext, fhirbundle, name, log); //Bundle Post was Throttled we can retry if (!fhirbundle.Success && fhirbundle.Status == System.Net.HttpStatusCode.TooManyRequests) { //Currently cannot use retry hints with EventGrid Trigger function bindings so we will throw and exception to enter eventgrid retry logic for FHIR Server throttling and do //our own dead letter for internal errors or unrecoverable conditions log.LogInformation($"ImportFHIRBUndles File Name:{name} is throttled..."); throw new Exception($"ImportFHIRBUndles: Transient Error File: {name}...Entering eventgrid retry process until success or ultimate failure to dead letter if configured."); } //No Errors move to processed container if (fhirbundle.Success && ((JArray)result["errors"]).Count == 0 && ((JArray)result["throttled"]).Count == 0) { await StorageUtils.MoveTo(cbclient, "bundles", "bundlesprocessed", name, $"{name}.processed", log); await StorageUtils.WriteStringToBlob(cbclient, "bundlesprocessed", $"{name}.processed.result", fhirbundle.Content, log); log.LogInformation($"ImportFHIRBUndles Processed file Name:{name}"); } //Handle Errors from FHIR Server of proxy if (!fhirbundle.Success || ((JArray)result["errors"]).Count > 0) { await StorageUtils.MoveTo(cbclient, "bundles", "bundleserr", name, $"{name}.err", log); await StorageUtils.WriteStringToBlob(cbclient, "bundleserr", $"{name}.err.response", fhirbundle.Content, log); await StorageUtils.WriteStringToBlob(cbclient, "bundleserr", $"{name}.err.actionneeded", result.ToString(), log); log.LogInformation($"ImportFHIRBUndles File Name:{name} had errors. Moved to deadletter bundleserr directory"); } //Handle Throttled Requests inside of bundle so we will create a new bundle to retry if (fhirbundle.Success && ((JArray)result["throttled"]).Count > 0) { var nb = NDJSONConverter.initBundle(); nb["entry"] = result["throttled"]; string fn = $"retry{Guid.NewGuid().ToString().Replace("-","")}.json"; await StorageUtils.MoveTo(cbclient, "bundles", "bundlesprocessed", name, $"{name}.processed", log); await StorageUtils.WriteStringToBlob(cbclient, "bundlesprocessed", $"{name}.processed.result", fhirbundle.Content, log); await StorageUtils.WriteStringToBlob(cbclient, "bundles", fn, nb.ToString(), log); log.LogInformation($"ImportFHIRBUndles File Name:{name} had throttled resources in response bundle. Moved to processed..Created retry bunde {fn}"); } }