/// <summary> /// Create an operation outcome resource /// </summary> public static OperationOutcome CreateOutcomeResource(FhirOperationResult result) { var retVal = new OperationOutcome(); Uri fhirIssue = new Uri("http://hl7.org/fhir/issue-type"); // Add issues for each of the details foreach (var dtl in result.Details) { Issue issue = new Issue() { Diagnostics = new DataTypes.FhirString(dtl.Message), Severity = (IssueSeverity)Enum.Parse(typeof(IssueSeverity), dtl.Type.ToString()) }; if (!String.IsNullOrEmpty(dtl.Location)) { issue.Location.Add(new DataTypes.FhirString(dtl.Location)); } // Type if (dtl.Exception is TimeoutException) { issue.Code = new DataTypes.FhirCoding(fhirIssue, "timeout"); } else if (dtl is FixedValueMisMatchedResultDetail) { issue.Code = new DataTypes.FhirCoding(fhirIssue, "value"); } else if (dtl is PersistenceResultDetail) { issue.Code = new DataTypes.FhirCoding(fhirIssue, "no-store"); } else { issue.Code = new DataTypes.FhirCoding(fhirIssue, "exception"); } retVal.Issue.Add(issue); } // Add detected issues if (result.Issues != null) { foreach (var iss in result.Issues) { retVal.Issue.Add(new Issue() { Diagnostics = new DataTypes.FhirString(iss.Text), Severity = (IssueSeverity)Enum.Parse(typeof(IssueSeverity), iss.Severity.ToString()), Code = new DataTypes.FhirCoding(fhirIssue, "business-rule") }); } } return(retVal); }
/// <summary> /// Delete a resource /// </summary> public DomainResourceBase DeleteResource(string resourceType, string id, string mimeType) { this.ThrowIfNotReady(); FhirOperationResult result = null; AuditData audit = null; IAuditorService auditService = ApplicationContext.Current.GetService(typeof(IAuditorService)) as IAuditorService; try { // Setup outgoing content/ WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NoContent; // Create or update? var handler = FhirResourceHandlerUtil.GetResourceHandler(resourceType); if (handler == null) { throw new FileNotFoundException(); // endpoint not found! } result = handler.Delete(id, TransactionMode.Commit); if (result == null || result.Outcome == ResultCode.Rejected) { throw new NotSupportedException(); } else if (result.Outcome == ResultCode.TypeNotAvailable) { throw new FileNotFoundException(String.Format("Resource {0} not found", WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri)); } else if (result.Outcome != ResultCode.Accepted) { throw new DataException("Delete failed"); } audit = AuditUtil.CreateAuditData(result.Results); return(null); } catch (Exception e) { audit = AuditUtil.CreateAuditData(null); audit.Outcome = OutcomeIndicator.EpicFail; return(this.ErrorHelper(e, result, false) as DomainResourceBase); } finally { if (auditService != null) { auditService.SendAudit(audit); } } }
/// <summary> /// Not implemented /// </summary> public Bundle GetHistory(string mimeType) { this.ThrowIfNotReady(); var result = new FhirOperationResult() { Outcome = ResultCode.Rejected, Details = new List <IResultDetail>() { new ResultDetail(ResultDetailType.Error, "For security reasons system history is not supported", null, null) } }; return(this.ErrorHelper(new NotImplementedException(), result, true) as Bundle); }
/// <summary> /// Validate a resource (really an update with debugging / non comit) /// </summary> public OperationOutcome ValidateResource(string resourceType, string id, DomainResourceBase target) { this.ThrowIfNotReady(); FhirOperationResult result = null; try { // Setup outgoing content // Create or update? var handler = FhirResourceHandlerUtil.GetResourceHandler(resourceType); if (handler == null) { throw new FileNotFoundException(); // endpoint not found! } result = handler.Update(id, target, TransactionMode.Rollback); if (result == null || result.Results.Count == 0) // Create { result = handler.Create(target, TransactionMode.Rollback); WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Created; } if (result == null || result.Outcome == ResultCode.Rejected) { throw new InvalidDataException("Resource structure is not valid"); } else if (result.Outcome == ResultCode.AcceptedNonConformant) { throw new ConstraintException("Resource not conformant"); } else if (result.Outcome == ResultCode.TypeNotAvailable) { throw new FileNotFoundException(String.Format("Resource {0} not found", WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri)); } else if (result.Outcome != ResultCode.Accepted) { throw new DataException("Validate failed"); } // Return constraint return(MessageUtil.CreateOutcomeResource(result)); } catch (Exception e) { return(this.ErrorHelper(e, result, false) as OperationOutcome); } }
/// <summary> /// Update a patient /// </summary> public SVC.Messaging.FHIR.FhirOperationResult Update(string id, SVC.Messaging.FHIR.Resources.ResourceBase target, SVC.Core.Services.DataPersistenceMode mode) { // Create a registration event ... subject FhirOperationResult retVal = new FhirOperationResult(); retVal.Details = new List <IResultDetail>(); // Get query parameters var resourceProcessor = FhirMessageProcessorUtil.GetMessageProcessor(this.ResourceName); // Parse the incoming request if (target != null) { target.Id = id; } var storeContainer = resourceProcessor.ProcessResource(target, retVal.Details); if (storeContainer == null) { retVal.Outcome = ResultCode.AcceptedNonConformant; } else { // Now store the container try { // HACK: Store the container storeContainer = DataUtil.Update(storeContainer, id, mode, retVal.Details); retVal.Outcome = ResultCode.Accepted; retVal.Results = new List <ResourceBase>(); retVal.Results.Add(resourceProcessor.ProcessComponent(storeContainer, retVal.Details)); } catch (MissingPrimaryKeyException e) { retVal.Details.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); retVal.Outcome = ResultCode.TypeNotAvailable; throw e; } catch (Exception e) { retVal.Details.Add(new ResultDetail(ResultDetailType.Error, ApplicationContext.LocalizationService.GetString("DTPE001"), e)); retVal.Outcome = ResultCode.Error; } } return(retVal); }
/// <summary> /// Get a resource's history /// </summary> public Bundle GetResourceInstanceHistory(string resourceType, string id, string mimeType) { this.ThrowIfNotReady(); FhirOperationResult readResult = null; try { readResult = this.PerformRead(resourceType, id, String.Empty); WebOperationContext.Current.OutgoingResponse.Headers.Remove("Content-Disposition"); return(MessageUtil.CreateBundle(readResult)); } catch (Exception e) { return(this.ErrorHelper(e, readResult, true) as Bundle); } }
/// <summary> /// Read resource with version /// </summary> public DomainResourceBase VReadResource(string resourceType, string id, string vid, string mimeType) { this.ThrowIfNotReady(); FhirOperationResult result = null; try { // Setup outgoing content result = this.PerformRead(resourceType, id, vid); return(result.Results[0]); } catch (Exception e) { return(this.ErrorHelper(e, result, false) as DomainResourceBase); } }
/// <summary> /// Read a reasource /// </summary> public DomainResourceBase ReadResource(string resourceType, string id, string mimeType) { this.ThrowIfNotReady(); FhirOperationResult result = null; try { // Setup outgoing content result = this.PerformRead(resourceType, id, null); String baseUri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.AbsoluteUri; WebOperationContext.Current.OutgoingResponse.Headers.Add("Content-Location", String.Format("{0}/_history/{1}", baseUri, result.Results[0].VersionId)); return(result.Results[0]); } catch (Exception e) { return(this.ErrorHelper(e, result, false) as DomainResourceBase); } }
/// <summary> /// Create a feed /// </summary> internal static Bundle CreateBundle(FhirOperationResult result) { Bundle retVal = new Bundle(); FhirQueryResult queryResult = result as FhirQueryResult; int pageNo = queryResult == null || queryResult.Query.Quantity == 0 ? 0 : queryResult.Query.Start / queryResult.Query.Quantity, nPages = queryResult == null || queryResult.Query.Quantity == 0 ? 1 : (queryResult.TotalResults / queryResult.Query.Quantity); retVal.Type = BundleType.SearchResults; retVal.Id = String.Format("urn:uuid:{0}", Guid.NewGuid()); // Make the Self uri String baseUri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri.AbsoluteUri; if (baseUri.Contains("?")) { baseUri = baseUri.Substring(0, baseUri.IndexOf("?") + 1); } else { baseUri += "?"; } // Self uri if (queryResult != null) { for (int i = 0; i < queryResult.Query.ActualParameters.Count; i++) { foreach (var itm in queryResult.Query.ActualParameters.GetValues(i)) { switch (queryResult.Query.ActualParameters.GetKey(i)) { case "_stateid": case "_page": break; default: baseUri += string.Format("{0}={1}&", queryResult.Query.ActualParameters.GetKey(i), itm); break; } } } if (!baseUri.Contains("_stateid=") && queryResult.Query.QueryId != Guid.Empty) { baseUri += String.Format("_stateid={0}&", queryResult.Query.QueryId); } } // Format string format = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["_format"]; if (String.IsNullOrEmpty(format)) { format = "xml"; } else if (format == "application/xml+fhir") { format = "xml"; } else if (format == "application/json+fhir") { format = "json"; } if (!baseUri.Contains("_format")) { baseUri += String.Format("_format={0}&", format); } var localizationService = ApplicationContext.Current.GetService <ILocalizationService>(); // Self URI if (queryResult != null && queryResult.TotalResults > queryResult.Results.Count) { retVal.Link.Add(new BundleLink(new Uri(String.Format("{0}_page={1}", baseUri, pageNo)), "self")); if (pageNo > 0) { retVal.Link.Add(new BundleLink(new Uri(String.Format("{0}_page=0", baseUri)), "first")); retVal.Link.Add(new BundleLink(new Uri(String.Format("{0}_page={1}", baseUri, pageNo - 1)), "previous")); } if (pageNo <= nPages) { retVal.Link.Add(new BundleLink(new Uri(String.Format("{0}_page={1}", baseUri, pageNo + 1)), "next")); retVal.Link.Add(new BundleLink(new Uri(String.Format("{0}_page={1}", baseUri, nPages + 1)), "last")); } } else { retVal.Link.Add(new BundleLink(new Uri(baseUri), "self")); } // Updated retVal.Timestamp = DateTime.Now; //retVal.Generator = "MARC-HI Service Core Framework"; // HACK: Remove me if (queryResult != null) { retVal.Total = queryResult.TotalResults; } //retVal. // Results if (result.Results != null) { var feedItems = new List <BundleEntry>(); foreach (DomainResourceBase itm in result.Results) { Uri resourceUrl = new Uri(String.Format("{0}/{1}?_format={2}", WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri, String.Format("{0}/{1}/_history/{2}", itm.GetType().Name, itm.Id, itm.VersionId), format)); BundleEntry feedResult = new BundleEntry(); //new Bundleentry(String.Format("{0} id {1} version {2}", itm.GetType().Name, itm.Id, itm.VersionId), null ,resourceUrl); feedResult.FullUrl = resourceUrl; string summary = "<div xmlns=\"http://www.w3.org/1999/xhtml\">" + itm.Text.ToString() + "</div>"; // Add confidence if the attribute permits ConfidenceAttribute confidence = itm.Attributes.Find(a => a is ConfidenceAttribute) as ConfidenceAttribute; if (confidence != null) { feedResult.Search = new BundleSearch() { Score = confidence.Confidence } } ; feedResult.Resource = new BundleResrouce(itm); feedItems.Add(feedResult); } retVal.Entry = feedItems; } // Outcome //if (result.Details.Count > 0 || result.Issues != null && result.Issues.Count > 0) //{ // var outcome = CreateOutcomeResource(result); // retVal.ElementExtensions.Add(outcome, new XmlSerializer(typeof(OperationOutcome))); // retVal.Description = new TextSyndicationContent(outcome.Text.ToString(), TextSyndicationContentKind.Html); //} return(retVal); }
/// <summary> /// Read a patint resource /// </summary> public SVC.Messaging.FHIR.FhirOperationResult Read(string id, string versionId) { FhirOperationResult result = new FhirOperationResult(); result.Details = new List <IResultDetail>(); result.Results = new List <ResourceBase>(); // Data persistence service IDataPersistenceService dataPersistence = ApplicationContext.CurrentContext.GetService(typeof(IDataPersistenceService)) as IDataPersistenceService; var container = dataPersistence.GetContainer(new VersionedDomainIdentifier() { Domain = this.DataDomain, Identifier = id, Version = String.IsNullOrEmpty(versionId) ? null : versionId }, String.IsNullOrEmpty(versionId)); // Container was not found if (container == null) { result.Outcome = ResultCode.NotAvailable; } else { var processor = FhirMessageProcessorUtil.GetComponentProcessor(container.GetType()); // Was there a history? if (versionId == null) { result.Results.Add(processor.ProcessComponent(container as IComponent, result.Details)); } else if (versionId == String.Empty) // Get all versions { while (container != null) { var hsrc = container as HealthServiceRecordContainer; var resource = processor.ProcessComponent(container as IComponent, result.Details); if (hsrc.IsMasked) // record is masked so add a detected issue { result.Issues.Add(new SVC.Core.Issues.DetectedIssue() { MitigatedBy = ManagementType.OtherActionTaken, Severity = IssueSeverityType.Moderate, Text = String.Format("{0}/_history/{1} will not be returned as it has been masked", resource.Id, resource.VersionId), Type = IssueType.DetectedIssue }); } else { result.Results.Add(resource); } container = hsrc.FindComponent(HealthServiceRecordSiteRoleType.OlderVersionOf) as IContainer; } } else // Some version { while (container != null) { var hsrc = container as HealthServiceRecordContainer; var resource = processor.ProcessComponent(container as IComponent, result.Details); container = hsrc.FindComponent(HealthServiceRecordSiteRoleType.ReplacementOf) as IContainer; if (resource != null && resource.VersionId.ToString() != versionId) { continue; } if (hsrc.IsMasked) // record is masked so add a detected issue { result.Issues.Add(new SVC.Core.Issues.DetectedIssue() { MitigatedBy = ManagementType.OtherActionTaken, Severity = IssueSeverityType.Moderate, Text = String.Format("{0}/_history/{1} will not be returned as it has been masked", resource.Id, resource.VersionId), Type = IssueType.DetectedIssue }); } else { result.Results.Add(resource); } } } result.Outcome = ResultCode.Accepted; } result.Results.RemoveAll(o => o == null); return(result); }
/// <summary> /// Perform a read against the underlying IFhirResourceHandler /// </summary> private FhirOperationResult PerformRead(string resourceType, string id, string vid) { this.ThrowIfNotReady(); // Get the services from the service registry var auditService = ApplicationContext.Current.GetService(typeof(IAuditorService)) as IAuditorService; // Stuff for auditing and exception handling AuditData audit = null; List <IResultDetail> details = new List <IResultDetail>(); FhirOperationResult result = null; try { // Get query parameters var queryParameters = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters; var resourceProcessor = FhirResourceHandlerUtil.GetResourceHandler(resourceType); if (resourceProcessor == null) // Unsupported resource { throw new FileNotFoundException("Specified resource type is not found"); } // TODO: Appropriately format response // Process incoming request result = resourceProcessor.Read(id, vid); if (result.Outcome == ResultCode.Rejected) { throw new InvalidDataException("Message was rejected"); } else if (result.Outcome == (ResultCode.NotAvailable | ResultCode.Rejected)) { throw new FileLoadException(String.Format("Resource {0} is no longer available", WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri)); } else if (result.Outcome == ResultCode.TypeNotAvailable || result.Results == null || result.Results.Count == 0) { throw new FileNotFoundException(String.Format("Resource {0} not found", WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri)); } else if (result.Outcome != ResultCode.Accepted) { throw new DataException("Read failed"); } audit = AuditUtil.CreateAuditData(result.Results); // Create the result if (result.Results != null && result.Results.Count > 0) { WebOperationContext.Current.OutgoingResponse.LastModified = result.Results[0].Timestamp; WebOperationContext.Current.OutgoingResponse.Headers.Add("Content-Disposition", String.Format("filename=\"{0}-{1}-{2}.xml\"", resourceType, result.Results[0].Id, result.Results[0].VersionId)); WebOperationContext.Current.OutgoingResponse.ETag = result.Results[0].VersionId; } return(result); } catch (Exception e) { audit = AuditUtil.CreateAuditData(null); audit.Outcome = OutcomeIndicator.EpicFail; throw; } finally { if (auditService != null) { auditService.SendAudit(audit); } } }
/// <summary> /// Throw an appropriate exception based on the caught exception /// </summary> private object ErrorHelper(Exception e, FhirOperationResult result, bool returnBundle) { if (result == null && returnBundle) { result = new FhirQueryResult() { Details = new List <IResultDetail>(), Query = new FhirQuery() { Start = 0, Quantity = 0 } } } ; else if (result == null) { result = new FhirOperationResult() { Details = new List <IResultDetail>() { new ResultDetail(ResultDetailType.Error, "No information available", e) } } } ; this.m_tracer.TraceEvent(TraceEventType.Error, 0, e.ToString()); result.Details.Add(new ResultDetail(ResultDetailType.Error, e.Message, e)); HttpStatusCode retCode = HttpStatusCode.OK; if (e is NotSupportedException) { retCode = System.Net.HttpStatusCode.MethodNotAllowed; } else if (e is NotImplementedException) { retCode = System.Net.HttpStatusCode.NotImplemented; } else if (e is InvalidDataException) { retCode = HttpStatusCode.BadRequest; } else if (e is FileLoadException) { retCode = System.Net.HttpStatusCode.Gone; } else if (e is FileNotFoundException || e is ArgumentException) { retCode = System.Net.HttpStatusCode.NotFound; } else if (e is ConstraintException) { retCode = (HttpStatusCode)422; } else { retCode = System.Net.HttpStatusCode.InternalServerError; } WebOperationContext.Current.OutgoingResponse.StatusCode = retCode; WebOperationContext.Current.OutgoingResponse.Format = WebMessageFormat.Xml; if (returnBundle) { throw new WebFaultException <Bundle>(MessageUtil.CreateBundle(result), retCode); } else { WebOperationContext.Current.OutgoingResponse.Headers.Add("Content-Disposition", "filename=\"error.xml\""); throw e; } //return MessageUtil.CreateOutcomeResource(result); }
/// <summary> /// Create a resource /// </summary> public DomainResourceBase CreateResource(string resourceType, string mimeType, DomainResourceBase target) { this.ThrowIfNotReady(); FhirOperationResult result = null; AuditData audit = null; IAuditorService auditService = ApplicationContext.Current.GetService(typeof(IAuditorService)) as IAuditorService; try { // Setup outgoing content // Create or update? var handler = FhirResourceHandlerUtil.GetResourceHandler(resourceType); if (handler == null) { throw new FileNotFoundException(); // endpoint not found! } result = handler.Create(target, TransactionMode.Commit); WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.Created; if (result == null || result.Outcome == ResultCode.Rejected) { throw new InvalidDataException("Resource structure is not valid"); } else if (result.Outcome == ResultCode.AcceptedNonConformant) { throw new ConstraintException("Resource not conformant"); } else if (result.Outcome == ResultCode.TypeNotAvailable) { throw new FileNotFoundException(String.Format("Resource {0} not found", WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri)); } else if (result.Outcome != ResultCode.Accepted) { throw new DataException("Create failed"); } audit = AuditUtil.CreateAuditData(result.Results); String baseUri = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.BaseUri.AbsoluteUri; WebOperationContext.Current.OutgoingResponse.Headers.Add("Content-Location", String.Format("{0}{1}/{2}/_history/{3}", baseUri, resourceType, result.Results[0].Id, result.Results[0].VersionId)); WebOperationContext.Current.OutgoingResponse.LastModified = result.Results[0].Timestamp; WebOperationContext.Current.OutgoingResponse.ETag = result.Results[0].VersionId; return(result.Results[0]); } catch (Exception e) { audit = AuditUtil.CreateAuditData(null); audit.Outcome = OutcomeIndicator.EpicFail; return(this.ErrorHelper(e, result, false) as DomainResourceBase); } finally { if (auditService != null) { auditService.SendAudit(audit); } } }
/// <summary> /// Read a value set /// </summary> public SVC.Messaging.FHIR.FhirOperationResult Read(string id, string versionId) { // Determine where to fetch the code system from List <Type> codeSystemType = new List <Type>(); if (id != null && id.StartsWith("v3-")) // Everest { id = id.Substring(3); foreach (var asm in new Assembly[] { typeof(MARC.Everest.DataTypes.II).Assembly, typeof(MARC.Everest.RMIM.UV.NE2008.Vocabulary.AdministrativeGender).Assembly, typeof(MARC.Everest.RMIM.CA.R020402.Vocabulary.AdministrativeGender).Assembly }) { if (!String.IsNullOrEmpty(versionId)) { var version = asm.GetCustomAttribute <AssemblyInformationalVersionAttribute>(); if (version != null && version.InformationalVersion != versionId) { continue; } } var tcodesys = Array.Find(asm.GetTypes(), t => t.IsEnum && t.GetCustomAttribute <StructureAttribute>() != null && t.GetCustomAttribute <StructureAttribute>().Name == id); if (tcodesys != null) { codeSystemType.Add(tcodesys); } } } else { foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { if (!String.IsNullOrEmpty(versionId) && asm.GetName().Version.ToString() != versionId) { continue; } var tcodesys = Array.Find(asm.GetTypes(), t => t.Name.Equals(id)); if (tcodesys != null) { codeSystemType.Add(tcodesys); } } } // Code system type if (codeSystemType == null || codeSystemType.Count == 0) { return new FhirOperationResult() { Outcome = ResultCode.TypeNotAvailable, } } ; else { var retVal = new FhirOperationResult() { Outcome = ResultCode.Accepted, Results = new List <ResourceBase>() }; foreach (var cs in codeSystemType) { retVal.Results.Add(this.CreateValueSetFromEnum(cs)); } return(retVal); } }