/// <summary> /// PErform a HEAD operation /// </summary> public bool Exists <TModel>(Guid key) { try { if (m_knownUuids.Contains(key)) { return(true); // Already tested } else if (m_knownUuids.Count > 1000) { m_knownUuids.Clear(); } HdsiServiceClient client = this.GetServiceClient(); //new ImsiServiceClient(ApplicationContext.Current.GetRestClient("imsi")); client.Client.Responding += (o, e) => this.Responding?.Invoke(o, e); client.Client.Credentials = this.GetCredentials(client.Client); if (client.Client.Credentials == null) { return(false); } this.m_tracer.TraceVerbose("Performing HDSI HEAD ({0}):{1}", typeof(TModel).FullName, key); var retVal = client.Client.Head($"{typeof(TModel).Name}/{key}"); if (retVal.ContainsKey("ETag")) { m_knownUuids.Add(key); } return(retVal.ContainsKey("ETag")); } catch (Exception) { return(false); } }
/// <summary> /// Determines whether the network is available. /// </summary> /// <returns>Returns true if the network is available.</returns> public bool IsAvailable() { try { //var restClient = ApplicationContext.Current.GetRestClient("hdsi"); var networkInformationService = ApplicationContext.Current.GetService <INetworkInformationService>(); if (networkInformationService.IsNetworkAvailable) { if (this.m_lastPing < DateTime.Now.AddSeconds(-30)) { HdsiServiceClient client = this.GetServiceClient(); //new HdsiServiceClient(restClient); client.Client.Credentials = new NullCredentials(); client.Client.Description.Endpoint[0].Timeout = 5000; this.m_lastPing = DateTime.Now; return(this.IsValidVersion(client) && client.Ping()); } return(true); } return(false); } catch (Exception e) { this.m_tracer.TraceInfo($"Unable to determine network state: {e}"); return(false); } }
/// <summary> /// Throws an exception if the specified service client has the invalid version /// </summary> private bool IsValidVersion(HdsiServiceClient client) { var expectedVersion = typeof(IdentifiedData).GetTypeInfo().Assembly.GetName().Version; if (this.m_options == null) { this.m_options = client.Options(); } if (this.m_options == null) { return(false); } var version = new Version(this.m_options.InterfaceVersion); // Major version must match & minor version must match. Example: // Server Client Result // 0.6.14.* 0.6.14.* Compatible // 0.7.0.* 0.6.14.* Not compatible (server newer) // 0.7.0.* 0.9.0.0 Compatible (client newer) // 0.8.0.* 1.0.0.0 Not compatible (major version mis-match) this.m_tracer.TraceVerbose("HDSI server indicates version {0}", this.m_options.InterfaceVersion); return(version < expectedVersion); }
/// <summary> /// Gets a specified model. /// </summary> /// <typeparam name="TModel">The type of model data to retrieve.</typeparam> /// <param name="key">The key of the model.</param> /// <param name="versionKey">The version key of the model.</param> /// <param name="options">The integrations query options.</param> /// <returns>Returns a model.</returns> public TModel Get <TModel>(Guid key, Guid?versionKey, IntegrationQueryOptions options = null) where TModel : IdentifiedData { try { HdsiServiceClient client = this.GetServiceClient(); //new HdsiServiceClient(ApplicationContext.Current.GetRestClient("hdsi")); client.Client.Requesting += IntegrationQueryOptions.CreateRequestingHandler(options); client.Client.Responding += (o, e) => this.Responding?.Invoke(o, e); client.Client.Credentials = this.GetCredentials(client.Client); if (client.Client.Credentials == null) { return(null); } this.m_tracer.TraceVerbose("Performing HDSI GET ({0}):{1}v{2}", typeof(TModel).FullName, key, versionKey); var retVal = client.Get <TModel>(key, versionKey); if (retVal is Bundle) { (retVal as Bundle)?.Reconstitute(); retVal = (retVal as Bundle).GetFocalObject(); } var integrationEvent = new IntegrationResultEventArgs(null, retVal); this.Responded?.Invoke(this, integrationEvent); return(integrationEvent.ResponseData as TModel); } catch (TargetInvocationException e) { throw Activator.CreateInstance(e.InnerException.GetType(), "Error performing action", e) as Exception; } }
/// <summary> /// Finds the specified model /// </summary> public Bundle Find <TModel>(Expression <Func <TModel, bool> > predicate, int offset, int?count, IntegrationQueryOptions options = null) where TModel : IdentifiedData { try { if (this.IsAvailable()) { HdsiServiceClient client = this.GetServiceClient(); client.Client.Requesting += IntegrationQueryOptions.CreateRequestingHandler(options); client.Client.Responding += (o, e) => this.Responding?.Invoke(o, e); client.Client.Credentials = this.GetCredentials(client.Client); if (client.Client.Credentials == null) { return(null); } if (options?.Timeout.HasValue == true) { client.Client.Description.Endpoint[0].Timeout = options.Timeout.Value; } this.m_tracer.TraceVerbose("Performing HDSI query ({0}):{1}", typeof(TModel).FullName, predicate); var retVal = client.Query <TModel>(predicate, offset, count, queryId: options?.QueryId); this.Responded?.Invoke(this, new IntegrationResultEventArgs(null, retVal)); //retVal?.Reconstitute(); return(retVal); } return(null); } catch (TargetInvocationException e) { throw Activator.CreateInstance(e.InnerException.GetType(), "Error performing action", e) as Exception; } }
/// <summary> /// Obsoletes specified data. /// </summary> /// <param name="data">The data to be obsoleted.</param> public void Obsolete(IdentifiedData data, bool unsafeObsolete = false) { try { if (!(data is Bundle || data is Entity || data is Act || data is EntityRelationship)) // || data is EntityRelationship)) { return; } HdsiServiceClient client = this.GetServiceClient(); //new HdsiServiceClient(ApplicationContext.Current.GetRestClient("hdsi")); client.Client.Credentials = this.GetCredentials(client.Client); client.Client.Responding += (o, e) => this.Responding?.Invoke(o, e); if (client.Client.Credentials == null) { return; } // Force an update if (unsafeObsolete) { client.Client.Requesting += (o, e) => e.AdditionalHeaders["X-SanteDB-Unsafe"] = "true"; } else { client.Client.Requesting += (o, e) => e.AdditionalHeaders["If-Match"] = data.Tag; } var method = typeof(HdsiServiceClient).GetRuntimeMethods().FirstOrDefault(o => o.Name == "Obsolete" && o.GetParameters().Length == 1); method = method.MakeGenericMethod(data.GetType()); this.m_tracer.TraceVerbose("Performing HDSI OBSOLETE {0}", data); var iver = method.Invoke(client, new object[] { data }) as IVersionedEntity; if (iver != null) { this.UpdateToServerCopy(iver, data as IVersionedEntity); } // Indicate that the server has responded this.Responded?.Invoke(this, new IntegrationResultEventArgs(data, iver as IdentifiedData)); } catch (TargetInvocationException e) { throw Activator.CreateInstance(e.InnerException.GetType(), "Error performing action", e) as Exception; } }
/// <summary> /// Get the difference between the server and this device's time /// </summary> public TimeSpan GetServerTimeDrift() { try { //var restClient = ApplicationContext.Current.GetRestClient("hdsi"); var networkInformationService = ApplicationContext.Current.GetService <INetworkInformationService>(); if (networkInformationService.IsNetworkAvailable) { HdsiServiceClient client = this.GetServiceClient(); //new HdsiServiceClient(restClient); client.Client.Credentials = new NullCredentials(); client.Client.Description.Endpoint[0].Timeout = 20000; var drift = TimeSpan.Zero; client.Client.Responded += (o, e) => { if (e.Headers != null) { if (e.Headers.ContainsKey("X-GeneratedOn")) { drift = DateTime.Parse(e.Headers["X-GeneratedOn"]).Subtract(DateTime.Now); } else if (DateTime.TryParse(e.Headers["Date"], out var serverTime)) { drift = serverTime.Subtract(DateTime.Now); } } }; client.Ping(); return(drift); } return(TimeSpan.Zero); } catch (Exception e) { this.m_tracer.TraceError($"Unable to determine server time drift: {e}"); return(TimeSpan.Zero); } }
/// <summary> /// Get service client /// </summary> private HdsiServiceClient GetServiceClient() { var retVal = new HdsiServiceClient(ApplicationContext.Current.GetRestClient("hdsi")); return(retVal); }
/// <summary> /// Updates specified data. /// </summary> /// <param name="data">The data to be updated.</param> public void Update(IdentifiedData data, bool unsafeUpdate = false) { try { // HACK if (!(data is Bundle || data is Entity || data is Act || data is Patch)) { return; } if (data is Patch && !typeof(Entity).GetTypeInfo().IsAssignableFrom((data as Patch).AppliesTo.Type.GetTypeInfo()) && !typeof(Act).GetTypeInfo().IsAssignableFrom((data as Patch).AppliesTo.Type.GetTypeInfo())) { return; } HdsiServiceClient client = this.GetServiceClient(); //new HdsiServiceClient(ApplicationContext.Current.GetRestClient("hdsi")); client.Client.Credentials = this.GetCredentials(client.Client); client.Client.Responding += (o, e) => this.Responding?.Invoke(o, e); if (client.Client.Credentials == null) { return; } // Force an update if (unsafeUpdate) { client.Client.Requesting += (o, e) => e.AdditionalHeaders["X-Patch-Force"] = "true"; } // Special case = Batch submit of data with an entry point var submission = (data as Bundle)?.GetFocalObject() ?? data; var existing = submission; // Assign a uuid for this submission if (data is Bundle && data.Key == null) { data.Key = Guid.NewGuid(); } // TODO: In MDM mode on the server patching will def cause an issue as we don't process the location header sent back from the server // we need to update the server to send back an appropriate location header and to fetch the local. if (submission is Patch) { var patch = submission as Patch; // Patch for update on times (obsolete, creation time, etc. always fail so lets remove them) patch.Operation.RemoveAll(o => o.OperationType == PatchOperationType.Test && this.m_removePatchTest.Contains(o.Path)); this.m_tracer.TraceVerbose("Performing HDSI UPDATE (PATCH) {0}", patch); var existingKey = patch.AppliesTo.Key; // Get the object and then update var idp = typeof(IDataPersistenceService <>).MakeGenericType(patch.AppliesTo.Type); var idpService = ApplicationContext.Current.GetService(idp) as IDataPersistenceService; existing = idpService.Get(existingKey.Value) as IdentifiedData; var newUuid = Guid.Empty; try { newUuid = client.Patch(patch); } catch (WebException e) { switch ((e.Response as HttpWebResponse).StatusCode) { case HttpStatusCode.Conflict: // Try to resolve the conflict in an automated way this.m_tracer.TraceWarning("Will attempt to force PATCH {0}", patch); // Condition 1: Can we apply the patch without causing any issues (ignoring version) client.Client.Requesting += (o, evt) => { evt.AdditionalHeaders["X-Patch-Force"] = "true"; }; // Configuration dictates only safe patch if (ApplicationContext.Current.Configuration.GetSection <SynchronizationConfigurationSection>().SafePatchOnly) { // First, let's grab the item var serverCopy = this.Get(patch.AppliesTo.Type, patch.AppliesTo.Key.Value, null); if (ApplicationContext.Current.GetService <IPatchService>().Test(patch, serverCopy)) { newUuid = client.Patch(patch); } else { // There are no intersections of properties between the object we have and the server copy var serverDiff = ApplicationContext.Current.GetService <IPatchService>().Diff(existing, serverCopy); if (!serverDiff.Operation.Any(sd => patch.Operation.Any(po => po.Path == sd.Path && sd.OperationType != PatchOperationType.Test))) { newUuid = client.Patch(patch); } else { throw; } } } else /// unsafe patch ... meh { newUuid = client.Patch(patch); } break; case HttpStatusCode.NotFound: // We tried to update something that doesn't exist on the server? That's odd this.m_tracer.TraceWarning("Server reported patch target doesn't exist! {0}", patch); var svcType = typeof(IDataPersistenceService <>).MakeGenericType(patch.AppliesTo.Type); var persistenceService = ApplicationContext.Current.GetService(svcType) as IDataPersistenceService; var localObject = persistenceService.Get(patch.AppliesTo.Key.Value); // Re-queue for create // First, we have to remove the "replaces version" key as it doesn't make much sense if (localObject is IVersionedEntity) { (localObject as IVersionedEntity).PreviousVersionKey = null; } this.Insert(Bundle.CreateBundle(localObject as IdentifiedData)); break; } } // Update the local version key to the server version key if (existing is IVersionedEntity iver) { this.m_tracer.TraceVerbose("Patch successful - VersionId of {0} to {1}", existing, newUuid); iver.VersionKey = newUuid; idpService.Update(existing); } } else // regular update { // Force an update if (!unsafeUpdate) { client.Client.Requesting += (o, e) => e.AdditionalHeaders["If-Match"] = data.Tag; } client.Client.Requesting += (o, e) => (e.Body as Bundle)?.Item.RemoveAll(i => !(i is Act || i is Patient || i is Provider || i is UserEntity)); // || i is EntityRelationship)); var method = typeof(HdsiServiceClient).GetRuntimeMethods().FirstOrDefault(o => o.Name == "Update" && o.GetParameters().Length == 1); method = method.MakeGenericMethod(submission.GetType()); this.m_tracer.TraceVerbose("Performing HDSI UPDATE (FULL) {0}", data); var iver = method.Invoke(client, new object[] { submission }) as IVersionedEntity; if (iver != null) { this.UpdateToServerCopy(iver, submission as IVersionedEntity); } // Notify updated this.Responded?.Invoke(this, new IntegrationResultEventArgs(existing, iver as IdentifiedData)); } } catch (TargetInvocationException e) { throw Activator.CreateInstance(e.InnerException.GetType(), "Error performing action", e) as Exception; } }
/// <summary> /// Inserts specified data. /// </summary> /// <param name="data">The data to be inserted.</param> public void Insert(IdentifiedData data) { try { if (!(data is Bundle || data is Entity || data is Act)) { return; } if (data is Bundle) { data.Key = null; } HdsiServiceClient client = this.GetServiceClient(); //new HdsiServiceClient(ApplicationContext.Current.GetRestClient("hdsi")); client.Client.Credentials = this.GetCredentials(client.Client); client.Client.Responding += (o, e) => this.Responding?.Invoke(o, e); if (client.Client.Credentials == null) { return; } // Special case = Batch submit of data with an entry point var submission = (data as Bundle)?.GetFocalObject() ?? data; client.Client.Requesting += (o, e) => { var bund = e.Body as Bundle; if (!(bund?.GetFocalObject() is UserEntity)) // not submitting a user entity so we only submit ACT { bund?.Item.RemoveAll(i => !(i is Act || i is Person && !(i is UserEntity))); // || i is EntityRelationship)); } if (bund != null) { bund.Key = Guid.NewGuid(); e.Cancel = bund.Item.Count == 0; } }; // Create method var method = typeof(HdsiServiceClient).GetRuntimeMethods().FirstOrDefault(o => o.Name == "Create" && o.GetParameters().Length == 1); method = method.MakeGenericMethod(submission.GetType()); this.m_tracer.TraceVerbose("Performing HDSI INSERT {0}", submission); var result = method.Invoke(client, new object[] { submission }) as IdentifiedData; if (result is IVersionedEntity iver) { this.UpdateToServerCopy(iver, data as IVersionedEntity); } else if (result is Bundle bundle) { var submissionBundle = data as Bundle; foreach (var itm in bundle.Item) { var original = submissionBundle.Item.FirstOrDefault(o => o.Key == itm.Key); if (itm is IVersionedEntity itmVer && original is IVersionedEntity) { this.UpdateToServerCopy(itmVer, original as IVersionedEntity); } } } this.Responded?.Invoke(this, new IntegrationResultEventArgs(data, result)); } catch (TargetInvocationException e) { throw Activator.CreateInstance(e.InnerException.GetType(), "Error performing action", e.InnerException) as Exception; } }