using Edge.Core.Configuration; using Edge.Core.Processor; using Edge.Core.UniversalApi.Auditing; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; namespace Edge.Core.UniversalApi { public class UniversalApiInvoker { private static JsonSerializerOptions jsonSerializerOptions; private IEnumerable<IAuditingStore> auditingStores; private IServiceProvider services; private bool config_DisableApiAuditing = false; private bool config_EnableApiInputAndOutputAuditing = false; static UniversalApiInvoker() { jsonSerializerOptions = new JsonSerializerOptions() { WriteIndented = true, PropertyNameCaseInsensitive = true, }; jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); } internal UniversalApiInvoker(IServiceProvider services) { this.services = services; var configurator = this.services.GetService<Configurator>(); this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false"); this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false"); this.auditingStores = services.GetRequiredService<IEnumerable<IAuditingStore>>(); configurator.OnConfigFileChanged += (_, __) => { this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false"); this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false"); }; } public async Task<object> InvokeUniversalApiServiceAsync(IProcessor processor, string apiServiceName, JsonElement[] jsonFormatParameters, RequestContext requestContext) { var pDescriptor = processor.ProcessorDescriptor(); #region AuditLogInfo auditLogInfo = null; if (!this.config_DisableApiAuditing) { auditLogInfo = new AuditLogInfo() { ClientIdentity = requestContext.ClientIdentity, DeviceHandlerOrAppName = pDescriptor.DeviceHandlerOrApp.ToString(), ExecutionTime = DateTime.Now, }; auditLogInfo.Actions.Add( new AuditLogActionInfo() { ApiName = "service : " + apiServiceName, InputParameters = this.config_EnableApiInputAndOutputAuditing ? jsonFormatParameters?.Select(jp => jp.GetRawText())?.Aggregate("", (acc, n) => acc + ", " + n) ?? "" : null, }); auditLogInfo.Prepare(); //logging early for knowing the calling is started this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray(); } #endregion try { var targetApiInfo = pDescriptor.UniversalApiInfos .FirstOrDefault(api => api.ServiceApiInfo != null && api.ServiceApiInfo.Name == apiServiceName && api.ServiceApiInfo.GetParameters().Length == (jsonFormatParameters?.Length ?? 0)); if (targetApiInfo == null) throw new ArgumentException("Could not find Universal Api in the processor has Service(Method) Parameter count == " + (jsonFormatParameters?.Length ?? 0)); var apiDefinedParameters = targetApiInfo.ServiceApiInfo.GetParameters(); List<object> ps = new List<object>(); for (int i = 0; i < apiDefinedParameters.Length; i++) { try { var para = JsonSerializer.Deserialize(jsonFormatParameters[i].GetRawText(), apiDefinedParameters[i].ParameterType, jsonSerializerOptions); ps.Add(para); } catch (Exception exxx) { throw new ArgumentException("Could not Parse input parameter[" + i + "] to type: " + apiDefinedParameters[i].ParameterType.Name + " for target Universal Api: " + apiServiceName + Environment.NewLine + exxx); } } var executeResult = await InvokeUniversalApiServiceAsync(processor, targetApiInfo.ServiceApiInfo, ps.ToArray(), requestContext); #region if (!this.config_DisableApiAuditing) { /* logging again as the service call has been done*/ if (this.config_EnableApiInputAndOutputAuditing) auditLogInfo.CommitWithExeResult(executeResult); else auditLogInfo.CommitWithExeResult(null); } #endregion return executeResult; } catch (Exception exxx) { #region if (!this.config_DisableApiAuditing) { auditLogInfo.CommitWithExceptions(new Exception[] { exxx }); } #endregion throw; } finally { #region if (!this.config_DisableApiAuditing) Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray()); #endregion } } public bool InvokeUniversalApiPropertySet(IProcessor processor, string apiPropertyName, JsonElement? propertySetValue, RequestContext requestContext) { var pDescriptor = processor.ProcessorDescriptor(); #region AuditLogInfo auditLogInfo = null; if (!this.config_DisableApiAuditing) { auditLogInfo = new AuditLogInfo() { ClientIdentity = requestContext.ClientIdentity, DeviceHandlerOrAppName = pDescriptor.DeviceHandlerOrApp.ToString(), ExecutionTime = DateTime.Now, }; auditLogInfo.Actions.Add( new AuditLogActionInfo() { ApiName = "property : " + apiPropertyName, InputParameters = this.config_EnableApiInputAndOutputAuditing ? propertySetValue?.GetRawText() ?? "" : null, }); //logging early for knowing the calling is started this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray(); } #endregion try { var targetApiInfo = pDescriptor?.UniversalApiInfos .FirstOrDefault(api => api.PropertyApiInfo != null && api.PropertyApiInfo.Name == apiPropertyName); if (targetApiInfo == null) throw new ArgumentException("Could not find Universal Api in the processor has Property with name: " + apiPropertyName); object setValue = null; if (propertySetValue == null) { } else setValue = JsonSerializer.Deserialize(propertySetValue.Value.GetRawText(), targetApiInfo.PropertyApiInfo.PropertyType, jsonSerializerOptions); var executeResult = InvokeUniversalApiPropertySet(processor, targetApiInfo.PropertyApiInfo, setValue, requestContext); #region if (!this.config_DisableApiAuditing) { /* logging again as the call has been done*/ if (this.config_EnableApiInputAndOutputAuditing) auditLogInfo.CommitWithExeResult(executeResult); else auditLogInfo.CommitWithExeResult(null); } #endregion return executeResult; } catch (Exception exxx) { #region if (!this.config_DisableApiAuditing) { auditLogInfo.CommitWithExceptions(new Exception[] { exxx }); } #endregion throw; } finally { #region if (!this.config_DisableApiAuditing) Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray()); #endregion } } /// <summary> /// Invoke the Get method for the property. /// </summary> /// <param name="processor"></param> /// <param name="apiPropertyName"></param> /// <returns>the value of the Property</returns> public object InvokeUniversalApiPropertyGet(IProcessor processor, string apiPropertyName, RequestContext requestContext) { var pDescriptor = processor.ProcessorDescriptor(); #region AuditLogInfo auditLogInfo = null; if (!this.config_DisableApiAuditing) { auditLogInfo = new AuditLogInfo() { ClientIdentity = requestContext.ClientIdentity, DeviceHandlerOrAppName = pDescriptor.DeviceHandlerOrApp.ToString(), ExecutionTime = DateTime.Now, }; auditLogInfo.Actions.Add( new AuditLogActionInfo() { ApiName = "property : " + apiPropertyName, InputParameters = null, }); auditLogInfo.Prepare(); //logging early for knowing the calling is started this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray(); } #endregion try { var targetPropertyInfo = pDescriptor.UniversalApiInfos.Where(apiInfo => (apiInfo.PropertyApiInfo?.Name ?? "") == apiPropertyName) .FirstOrDefault()?.PropertyApiInfo; if (targetPropertyInfo == null) throw new EntryPointNotFoundException("Could not find Universal Api Property with name: " + (targetPropertyInfo.Name ?? "")); if (!targetPropertyInfo.CanRead || targetPropertyInfo.GetMethod == null || (targetPropertyInfo.GetMethod?.IsPrivate ?? false)) { throw new EntryPointNotFoundException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "") + " does not allow Get"); } try { /* indicates 'get' operation*/ dynamic executeResult = null; if (pDescriptor.ProcessorType == ProcessorType.Application) executeResult = targetPropertyInfo.GetGetMethod().Invoke(processor, null); else if (pDescriptor.ProcessorType == ProcessorType.DeviceProcessor) executeResult = targetPropertyInfo.GetGetMethod().Invoke(pDescriptor.DeviceHandlerOrApp, null); #region if (!this.config_DisableApiAuditing) { /* logging again as the call has been done*/ if (this.config_EnableApiInputAndOutputAuditing) auditLogInfo.CommitWithExeResult(executeResult); else auditLogInfo.CommitWithExeResult(null); } #endregion return executeResult; } catch (Exception exx) { throw;// new InvalidOperationException("Invoke UniversalApi Property: " + apiPropertyName + " exceptioned, detail: " + exx.ToString()); } } catch (Exception exxx) { #region if (!this.config_DisableApiAuditing) { auditLogInfo.CommitWithExceptions(new Exception[] { exxx }); } #endregion throw; } finally { #region if (!this.config_DisableApiAuditing) Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray()); #endregion } } private async Task<object> InvokeUniversalApiServiceAsync(IProcessor processor, MethodInfo apiServiceInfo, object[] methodParameters, RequestContext requestContext) { var descriptor = processor.ProcessorDescriptor(); var targetMethodInfo = descriptor.UniversalApiInfos.Where(apiInfo => (apiInfo.ServiceApiInfo?.Name ?? "") == apiServiceInfo.Name) .FirstOrDefault()?.ServiceApiInfo; if (targetMethodInfo == null) throw new EntryPointNotFoundException("Could not find Universal Api Method with name: " + (apiServiceInfo.Name ?? "")); try { dynamic result = null; if (descriptor.ProcessorType == ProcessorType.Application) result = await (dynamic)targetMethodInfo.Invoke(processor, methodParameters); else if (descriptor.ProcessorType == ProcessorType.DeviceProcessor) result = await (dynamic)targetMethodInfo.Invoke(descriptor.DeviceHandlerOrApp, methodParameters); return result; } catch (Exception exx) { throw;// new InvalidOperationException("Invoke UniversalApi Service: " + apiServiceInfo.Name + " exceptioned, detail: " + exx.ToString()); } } /// <summary> /// Invoke the Set method for the property, if set value successfully, will return true, otherwise false. /// </summary> /// <param name="processor"></param> /// <param name="apiPropertyInfo"></param> /// <param name="propertySetValue">the value will be set to Property</param> /// <returns>if set value successfully, will return true, otherwise false.</returns> private bool InvokeUniversalApiPropertySet(IProcessor processor, PropertyInfo apiPropertyInfo, object propertySetValue, RequestContext requestContext) { var descriptor = processor.ProcessorDescriptor(); var targetPropertyInfo = descriptor.UniversalApiInfos.Where(apiInfo => (apiInfo.PropertyApiInfo?.Name ?? "") == apiPropertyInfo.Name) .FirstOrDefault()?.PropertyApiInfo; if (targetPropertyInfo == null) throw new EntryPointNotFoundException("Could not find Universal Api Property with name: " + (targetPropertyInfo.Name ?? "")); if (!targetPropertyInfo.CanWrite || targetPropertyInfo.SetMethod == null || (targetPropertyInfo.SetMethod?.IsPrivate ?? false)) { throw new EntryPointNotFoundException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "") + " does not allow Set"); } if (propertySetValue == null && !targetPropertyInfo.PropertyType.IsClass && !(targetPropertyInfo.PropertyType.IsGenericType && targetPropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))) throw new ArgumentException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "") + " does not compatible has its Set type(" + targetPropertyInfo.PropertyType + ") to Null"); if (propertySetValue != null && !targetPropertyInfo.PropertyType.IsAssignableFrom(propertySetValue?.GetType())) throw new ArgumentException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "") + " does not compatible has its Set type(" + targetPropertyInfo.PropertyType + ") to " + (propertySetValue?.GetType().Name ?? "null")); try { /* indicates 'set' operation*/ if (descriptor.ProcessorType == ProcessorType.Application) targetPropertyInfo.SetValue(processor, propertySetValue); else if (descriptor.ProcessorType == ProcessorType.DeviceProcessor) targetPropertyInfo.SetValue(descriptor.DeviceHandlerOrApp, propertySetValue); return true; } catch (Exception exx) { throw;// new InvalidOperationException("Invoke UniversalApi Property: " + apiPropertyInfo.Name + " exceptioned, detail: " + exx.ToString()); } } } }