/// <summary> /// Searches a resource from the client registry datastore /// </summary> public Bundle SearchResource(string resourceType) { 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>(); FhirQueryResult result = null; try { // Get query parameters var queryParameters = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters; var resourceProcessor = FhirResourceHandlerUtil.GetResourceHandler(resourceType); // Setup outgoing content WebOperationContext.Current.OutgoingRequest.Headers.Add("Last-Modified", DateTime.Now.ToString("ddd, dd MMM yyyy HH:mm:ss zzz")); if (resourceProcessor == null) // Unsupported resource { throw new FileNotFoundException(); } // TODO: Appropriately format response // Process incoming request result = resourceProcessor.Query(WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters); if (result == null || result.Outcome == ResultCode.Rejected) { throw new InvalidDataException("Message was rejected"); } else if (result.Outcome != ResultCode.Accepted) { throw new DataException("Query failed"); } audit = AuditUtil.CreateAuditData(result.Results); // Create the Atom feed return(MessageUtil.CreateBundle(result)); } catch (Exception e) { audit = AuditUtil.CreateAuditData(null); audit.Outcome = OutcomeIndicator.EpicFail; return(this.ErrorHelper(e, result, true) as Bundle); } finally { if (auditService != null) { auditService.SendAudit(audit); } } }
/// <summary> /// Query data from the client registry /// </summary> public SVC.Messaging.FHIR.FhirQueryResult Query(System.Collections.Specialized.NameValueCollection parameters) { FhirQueryResult result = new FhirQueryResult(); result.Details = new List <IResultDetail>(); // Get query parameters var resourceProcessor = FhirMessageProcessorUtil.GetMessageProcessor(this.ResourceName); // Process incoming request NameValueCollection goodParameters = new NameValueCollection(); for (int i = 0; i < parameters.Count; i++) { for (int v = 0; v < parameters.GetValues(i).Length; v++) { if (!String.IsNullOrEmpty(parameters.GetValues(i)[v])) { goodParameters.Add(parameters.GetKey(i), MessageUtil.Escape(parameters.GetValues(i)[v])); } } } parameters = goodParameters; var queryObject = resourceProcessor.ParseQuery(goodParameters, result.Details); result.Query = queryObject; // sanity check #if !DEBUG if (result.Query.ActualParameters.Count == 0) { result.Outcome = ResultCode.Rejected; result.Details.Add(new ValidationResultDetail(ResultDetailType.Error, ApplicationContext.LocalizationService.GetString("MSGE077"), null, null)); } else #endif if (result.Details.Exists(o => o.Type == ResultDetailType.Error)) { result.Outcome = ResultCode.Error; result.Details.Add(new ResultDetail(ResultDetailType.Error, ApplicationContext.LocalizationService.GetString("MSGE00A"), null, null)); } else if (queryObject.Filter == null || result.Outcome != ResultCode.Accepted) { throw new InvalidOperationException("Could not process query parameters!"); } else { result = DataUtil.Query(queryObject, result.Details); } return(result); }
/// <summary> /// Reads the complete history of the specified identifier /// </summary> public Bundle History(string id) { if (String.IsNullOrEmpty(id)) { this.m_traceSource.TraceError($"Argument {nameof(id)} null or empty"); throw new ArgumentNullException(this.m_localizationService.GetString("error.type.ArgumentNullException")); } Guid guidId = Guid.Empty; if (!Guid.TryParse(id, out guidId)) { throw new ArgumentException(this.m_localizationService.FormatString("error.type.ArgumentException", new { param = "id" })); } var result = this.Read(guidId, Guid.Empty); if (result == null) { throw new KeyNotFoundException(this.m_localizationService.GetString("error.type.KeyNotFoundException")); } // Results List <TModel> results = new List <TModel>() { result }; while ((result as IVersionedEntity)?.PreviousVersionKey.HasValue == true) { result = this.Read(guidId, (result as IVersionedEntity).PreviousVersionKey.Value); results.Add(result); } // FHIR Operation result var retVal = new FhirQueryResult(typeof(TFhirResource).Name) { Results = results.Select(this.MapToFhir).Select(o => new Bundle.EntryComponent() { Resource = o, Response = new Bundle.ResponseComponent() { Status = "200" } }).ToList() }; return(ExtensionUtil.ExecuteBeforeSendResponseBehavior(TypeRestfulInteraction.HistoryInstance, this.ResourceType, MessageUtil.CreateBundle(retVal, Bundle.BundleType.History)) as Bundle); }
/// <summary> /// Parameters /// </summary> public override Bundle Query(NameValueCollection parameters) { if (parameters == null) { this.m_tracer.TraceError(nameof(parameters)); throw new ArgumentNullException(nameof(parameters), this.m_localizationService.GetString("error.type.ArgumentNullException")); } Core.Model.Query.NameValueCollection hdsiQuery = null; var query = QueryRewriter.RewriteFhirQuery(typeof(Observation), typeof(Core.Model.Acts.Observation), parameters, out hdsiQuery); // Do the query var totalResults = 0; IEnumerable <Core.Model.Acts.Observation> hdsiResults = null; if (parameters["value-concept"] != null) { var predicate = QueryExpressionParser.BuildLinqExpression <CodedObservation>(hdsiQuery); hdsiResults = this.QueryEx(predicate, query.QueryId, query.Start, query.Quantity, out totalResults).OfType <Core.Model.Acts.Observation>(); } else if (parameters["value-quantity"] != null) { var predicate = QueryExpressionParser.BuildLinqExpression <QuantityObservation>(hdsiQuery); hdsiResults = this.QueryEx(predicate, query.QueryId, query.Start, query.Quantity, out totalResults).OfType <Core.Model.Acts.Observation>(); } else { var predicate = QueryExpressionParser.BuildLinqExpression <Core.Model.Acts.Observation>(hdsiQuery); hdsiResults = this.Query(predicate, query.QueryId, query.Start, query.Quantity, out totalResults); } // Return FHIR query result var retVal = new FhirQueryResult("Observation") { Results = hdsiResults.Select(this.MapToFhir).Select(o => new Bundle.EntryComponent { Resource = o, Search = new Bundle.SearchComponent { Mode = Bundle.SearchEntryMode.Match } }).ToList(), Query = query, TotalResults = totalResults }; base.ProcessIncludes(hdsiResults, parameters, retVal); return(ExtensionUtil.ExecuteBeforeSendResponseBehavior(TypeRestfulInteraction.SearchType, this.ResourceType, MessageUtil.CreateBundle(retVal, Bundle.BundleType.Searchset)) as Bundle); }
/// <summary> /// Queries for a specified resource. /// </summary> /// <param name="parameters">The parameters.</param> /// <returns>Returns the FHIR query result containing the results of the query.</returns> /// <exception cref="System.ArgumentNullException">parameters</exception> public virtual Bundle Query(System.Collections.Specialized.NameValueCollection parameters) { if (parameters == null) { this.m_traceSource.TraceError($"Argument {nameof(parameters)} null or empty"); throw new ArgumentNullException(this.m_localizationService.GetString("error.type.ArgumentNullException")); } Core.Model.Query.NameValueCollection hdsiQuery = null; FhirQuery query = QueryRewriter.RewriteFhirQuery(typeof(TFhirResource), typeof(TModel), parameters, out hdsiQuery); // Do the query int totalResults = 0; var predicate = QueryExpressionParser.BuildLinqExpression <TModel>(hdsiQuery); var hdsiResults = this.Query(predicate, query.QueryId, query.Start, query.Quantity, out totalResults); var auth = AuthenticationContext.Current; // Return FHIR query result if (Environment.ProcessorCount > 4) { hdsiResults = hdsiResults.AsParallel().AsOrdered(); } var retVal = new FhirQueryResult(typeof(TFhirResource).Name) { Results = hdsiResults.Select(o => { using (AuthenticationContext.EnterContext(auth.Principal)) { return(new Bundle.EntryComponent() { Resource = this.MapToFhir(o), Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Match } }); } }).ToList(), Query = query, TotalResults = totalResults }; this.ProcessIncludes(hdsiResults, parameters, retVal); return(ExtensionUtil.ExecuteBeforeSendResponseBehavior(TypeRestfulInteraction.SearchType, this.ResourceType, MessageUtil.CreateBundle(retVal, Bundle.BundleType.Searchset)) as Bundle); }
/// <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> /// Query the data store /// </summary> public static FhirQueryResult Query(ClientRegistryFhirQuery querySpec, List <IResultDetail> details) { // Get the services IClientRegistryDataService dataService = ApplicationContext.CurrentContext.GetService(typeof(IClientRegistryDataService)) as IClientRegistryDataService; IQueryPersistenceService queryService = ApplicationContext.CurrentContext.GetService(typeof(IQueryPersistenceService)) as IQueryPersistenceService; try { if (querySpec.Quantity > 100) { throw new ConstraintException("Query limit must not exceed 100"); } if (dataService == null) { throw new InvalidOperationException("No persistence service has been configured, queries cannot continue without this service"); } FhirQueryResult result = new FhirQueryResult(); result.Query = querySpec; result.Issues = new List <DetectedIssue>(); result.Details = details; result.Results = new List <SVC.Messaging.FHIR.Resources.ResourceBase>(querySpec.Quantity); VersionedDomainIdentifier[] identifiers; RegistryQueryRequest queryRequest = new RegistryQueryRequest() { QueryRequest = querySpec.Filter, QueryId = querySpec.QueryId != Guid.Empty ? querySpec.QueryId.ToString() : null, QueryTag = querySpec.QueryId != Guid.Empty ? querySpec.QueryId.ToString(): null, IsSummary = !querySpec.IncludeHistory, Offset = querySpec.Start, Limit = querySpec.Quantity }; // Is this a continue? queryRequest.IsContinue = (!String.IsNullOrEmpty(queryRequest.QueryId) && queryRequest.Offset > 0); var dataResults = dataService.Query(queryRequest); details.AddRange(dataResults.Details); result.TotalResults = dataResults.TotalResults; // Fetch the results foreach (HealthServiceRecordContainer res in dataResults.Results) { if (res == null) { continue; } var resultSubject = res.FindComponent(HealthServiceRecordSiteRoleType.SubjectOf) as HealthServiceRecordContainer ?? res; var processor = FhirMessageProcessorUtil.GetComponentProcessor(resultSubject.GetType()); if (processor == null) { result.Details.Add(new NotImplementedResultDetail(ResultDetailType.Error, String.Format("Will not include {1}^^^&{2}&ISO in result set, cannot find converter for {0}", resultSubject.GetType().Name, resultSubject.Id, ApplicationContext.ConfigurationService.OidRegistrar.GetOid("CR_CID").Oid), null, null)); } else { result.Results.Add(processor.ProcessComponent(resultSubject, details)); } } // Sort control? // TODO: Support sort control but for now just sort according to confidence then date //retVal.Sort((a, b) => b.Id.CompareTo(a.Id)); // Default sort by id //if (queryPersistence != null) // result.TotalResults = (int)queryPersistence.QueryResultTotalQuantity(querySpec.QueryId.ToString()); //else // result.TotalResults = retRecordId.Count(o => o != null); return(result); } catch (Exception ex) { Trace.TraceError(ex.ToString()); details.Add(new PersistenceResultDetail(ResultDetailType.Error, ex.Message, ex)); throw; } }
/// <summary> /// Process includes for the specified result set /// </summary> protected virtual void ProcessIncludes(IEnumerable <TModel> results, System.Collections.Specialized.NameValueCollection parameters, FhirQueryResult queryResult) { // Include or ref include? if (parameters["_include"] != null) // TODO: _include:iterate (fhir is crazy) { queryResult.Results = queryResult.Results.Union(results.SelectMany(h => this.GetIncludes(h, parameters["_include"].Split(',').Select(o => new IncludeInstruction(o)))).Select(o => new Bundle.EntryComponent() { Resource = o, Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Include } })).ToList(); } if (parameters["_revinclude"] != null) // TODO: _revinclude:iterate (fhir is crazy) { queryResult.Results = queryResult.Results.Union(results.SelectMany(h => this.GetReverseIncludes(h, parameters["_revinclude"].Split(',').Select(o => new IncludeInstruction(o)))).Select(o => new Bundle.EntryComponent() { Resource = o, Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Include } })).ToList(); } }
/// <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 feed /// </summary> public static Bundle CreateBundle(FhirQueryResult result, Bundle.BundleType bundleType) { Bundle retVal = new Bundle(); FhirQueryResult queryResult = result as FhirQueryResult; retVal.Id = String.Format("urn:uuid:{0}", Guid.NewGuid()); retVal.Type = bundleType; // Make the Self uri String baseUri = $"{result.ResourceType}"; if (queryResult.Query != null) { 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 = Bundle.BundleType.Searchset; var queryUri = 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": case "_count": break; default: queryUri += string.Format("{0}={1}&", queryResult.Query.ActualParameters.GetKey(i), itm); break; } } } if (!baseUri.Contains("_stateid=") && queryResult.Query.QueryId != Guid.Empty) { queryUri += String.Format("_stateid={0}&", queryResult.Query.QueryId); } } // Self URI if (queryResult != null && queryResult.TotalResults > queryResult.Results.Count) { retVal.Link.Add(new Bundle.LinkComponent() { Url = $"{queryUri}_page={pageNo}&_count={queryResult?.Query.Quantity ?? 100}", Relation = "self" }); if (pageNo > 0) { retVal.Link.Add(new Bundle.LinkComponent() { Url = $"{queryUri}_page=0&_count={queryResult?.Query.Quantity ?? 100}", Relation = "first" }); retVal.Link.Add(new Bundle.LinkComponent() { Url = $"{queryUri}_page={pageNo - 1}&_count={queryResult?.Query.Quantity ?? 100}", Relation = "previous" }); } if (pageNo <= nPages) { retVal.Link.Add(new Bundle.LinkComponent() { Url = $"{queryUri}_page={pageNo + 1}&_count={queryResult?.Query.Quantity ?? 100}", Relation = "next" }); retVal.Link.Add(new Bundle.LinkComponent() { Url = $"{queryUri}_page={nPages}&_count={queryResult?.Query.Quantity ?? 100}", Relation = "last" }); } } else { retVal.Link.Add(new Bundle.LinkComponent() { Url = queryUri, Relation = "self" }); } } else //History { // History type retVal.Type = Bundle.BundleType.History; // Self URI retVal.Link.Add(new Bundle.LinkComponent() { Url = $"{baseUri}/_history", Relation = "self" }); } // Updated retVal.Timestamp = DateTime.Now; //retVal.Generator = "MARC-HI Service Core Framework"; // HACK: Remove me if (queryResult != null) { retVal.Total = queryResult.TotalResults; } // Results if (result.Results != null) { retVal.Entry = result.Results.Select(itm => { itm.Link = new List <Bundle.LinkComponent>() { new Bundle.LinkComponent() { Relation = "_self", Url = itm.Resource.HasVersionId ? $"{itm.Resource.TypeName}/{itm.Resource.Id}/_history/{itm.Resource.VersionId}" : $"{itm.Resource.TypeName}/{itm.Resource.Id}" } }; itm.FullUrl = itm.FullUrl ?? $"{GetBaseUri()}/{itm.Resource.TypeName}/{itm.Resource.Id}"; // Add confidence if the attribute permits if (itm.Search != null && itm.Search.Mode == Bundle.SearchEntryMode.Match) // Search data { var confidence = itm.Annotations(typeof(ITag)).OfType <ITag>().FirstOrDefault(t => t.TagKey == "$conf"); if (confidence != null) { itm.Search.Score = Decimal.Parse(confidence.Value); } } return(itm); }).ToList(); } // Outcome //if (result.Details.Count > 0 || result.Issues != null && result.Issues.Count > 0) //{ // var outcome = CreateOutcomeResource(result); // retVal.ElementExtensions.Add(outcome, XmlModelSerializerFactory.Current.CreateSerializer(typeof(OperationOutcome))); // retVal.Description = new TextSyndicationContent(outcome.Text.ToString(), TextSyndicationContentKind.Html); //} return(retVal); }