UniversalApiInvoker.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. using Edge.Core.Configuration;
  2. using Edge.Core.Processor;
  3. using Edge.Core.UniversalApi.Auditing;
  4. using Microsoft.Extensions.DependencyInjection;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Reflection;
  9. using System.Text;
  10. using System.Text.Json;
  11. using System.Text.Json.Serialization;
  12. using System.Threading.Tasks;
  13. namespace Edge.Core.UniversalApi
  14. {
  15. public class UniversalApiInvoker
  16. {
  17. private static JsonSerializerOptions jsonSerializerOptions;
  18. private IEnumerable<IAuditingStore> auditingStores;
  19. private IServiceProvider services;
  20. private bool config_DisableApiAuditing = false;
  21. private bool config_EnableApiInputAndOutputAuditing = false;
  22. static UniversalApiInvoker()
  23. {
  24. jsonSerializerOptions = new JsonSerializerOptions()
  25. {
  26. WriteIndented = true,
  27. PropertyNameCaseInsensitive = true,
  28. };
  29. jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
  30. }
  31. internal UniversalApiInvoker(IServiceProvider services)
  32. {
  33. this.services = services;
  34. var configurator = this.services.GetService<Configurator>();
  35. this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  36. ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  37. this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  38. ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  39. this.auditingStores = services.GetRequiredService<IEnumerable<IAuditingStore>>();
  40. configurator.OnConfigFileChanged += (_, __) =>
  41. {
  42. this.config_DisableApiAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  43. ?.FirstOrDefault(p => p.Name.Equals("disableUniversalApiAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  44. this.config_EnableApiInputAndOutputAuditing = bool.Parse(configurator.MetaConfiguration.Parameter
  45. ?.FirstOrDefault(p => p.Name.Equals("enableUniversalApiInputAndOutputAuditing", StringComparison.OrdinalIgnoreCase))?.Value ?? "false");
  46. };
  47. }
  48. public async Task<object> InvokeUniversalApiServiceAsync(IProcessor processor,
  49. string apiServiceName, JsonElement[] jsonFormatParameters,
  50. RequestContext requestContext)
  51. {
  52. var pDescriptor = processor.ProcessorDescriptor();
  53. #region
  54. AuditLogInfo auditLogInfo = null;
  55. if (!this.config_DisableApiAuditing)
  56. {
  57. auditLogInfo = new AuditLogInfo()
  58. {
  59. ClientIdentity = requestContext.ClientIdentity,
  60. DeviceHandlerOrAppName = pDescriptor.DeviceHandlerOrApp.ToString(),
  61. ExecutionTime = DateTime.Now,
  62. };
  63. auditLogInfo.Actions.Add(
  64. new AuditLogActionInfo()
  65. {
  66. ApiName = "service : " + apiServiceName,
  67. InputParameters =
  68. this.config_EnableApiInputAndOutputAuditing ?
  69. jsonFormatParameters?.Select(jp => jp.GetRawText())?.Aggregate("", (acc, n) => acc + ", " + n) ?? ""
  70. : null,
  71. });
  72. auditLogInfo.Prepare();
  73. //logging early for knowing the calling is started
  74. this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray();
  75. }
  76. #endregion
  77. try
  78. {
  79. var targetApiInfo = pDescriptor.UniversalApiInfos
  80. .FirstOrDefault(api => api.ServiceApiInfo != null
  81. && api.ServiceApiInfo.Name == apiServiceName
  82. && api.ServiceApiInfo.GetParameters().Length == (jsonFormatParameters?.Length ?? 0));
  83. if (targetApiInfo == null) throw new ArgumentException("Could not find Universal Api in the processor has Service(Method) Parameter count == " + (jsonFormatParameters?.Length ?? 0));
  84. var apiDefinedParameters = targetApiInfo.ServiceApiInfo.GetParameters();
  85. List<object> ps = new List<object>();
  86. for (int i = 0; i < apiDefinedParameters.Length; i++)
  87. {
  88. try
  89. {
  90. var para = JsonSerializer.Deserialize(jsonFormatParameters[i].GetRawText(), apiDefinedParameters[i].ParameterType, jsonSerializerOptions);
  91. ps.Add(para);
  92. }
  93. catch (Exception exxx)
  94. {
  95. throw new ArgumentException("Could not Parse input parameter[" + i + "] to type: "
  96. + apiDefinedParameters[i].ParameterType.Name + " for target Universal Api: " + apiServiceName
  97. + Environment.NewLine + exxx);
  98. }
  99. }
  100. var executeResult = await InvokeUniversalApiServiceAsync(processor, targetApiInfo.ServiceApiInfo, ps.ToArray(), requestContext);
  101. #region
  102. if (!this.config_DisableApiAuditing)
  103. {
  104. /* logging again as the service call has been done*/
  105. if (this.config_EnableApiInputAndOutputAuditing)
  106. auditLogInfo.CommitWithExeResult(executeResult);
  107. else
  108. auditLogInfo.CommitWithExeResult(null);
  109. }
  110. #endregion
  111. return executeResult;
  112. }
  113. catch (Exception exxx)
  114. {
  115. #region
  116. if (!this.config_DisableApiAuditing)
  117. { auditLogInfo.CommitWithExceptions(new Exception[] { exxx }); }
  118. #endregion
  119. throw;
  120. }
  121. finally
  122. {
  123. #region
  124. if (!this.config_DisableApiAuditing)
  125. Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray());
  126. #endregion
  127. }
  128. }
  129. public bool InvokeUniversalApiPropertySet(IProcessor processor,
  130. string apiPropertyName, JsonElement? propertySetValue,
  131. RequestContext requestContext)
  132. {
  133. var pDescriptor = processor.ProcessorDescriptor();
  134. #region
  135. AuditLogInfo auditLogInfo = null;
  136. if (!this.config_DisableApiAuditing)
  137. {
  138. auditLogInfo = new AuditLogInfo()
  139. {
  140. ClientIdentity = requestContext.ClientIdentity,
  141. DeviceHandlerOrAppName = pDescriptor.DeviceHandlerOrApp.ToString(),
  142. ExecutionTime = DateTime.Now,
  143. };
  144. auditLogInfo.Actions.Add(
  145. new AuditLogActionInfo()
  146. {
  147. ApiName = "property : " + apiPropertyName,
  148. InputParameters =
  149. this.config_EnableApiInputAndOutputAuditing ?
  150. propertySetValue?.GetRawText() ?? ""
  151. : null,
  152. });
  153. //logging early for knowing the calling is started
  154. this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray();
  155. }
  156. #endregion
  157. try
  158. {
  159. var targetApiInfo = pDescriptor?.UniversalApiInfos
  160. .FirstOrDefault(api => api.PropertyApiInfo != null
  161. && api.PropertyApiInfo.Name == apiPropertyName);
  162. if (targetApiInfo == null) throw new ArgumentException("Could not find Universal Api in the processor has Property with name: " + apiPropertyName);
  163. object setValue = null;
  164. if (propertySetValue == null)
  165. {
  166. }
  167. else
  168. setValue = JsonSerializer.Deserialize(propertySetValue.Value.GetRawText(),
  169. targetApiInfo.PropertyApiInfo.PropertyType, jsonSerializerOptions);
  170. var executeResult = InvokeUniversalApiPropertySet(processor, targetApiInfo.PropertyApiInfo, setValue, requestContext);
  171. #region
  172. if (!this.config_DisableApiAuditing)
  173. {
  174. /* logging again as the call has been done*/
  175. if (this.config_EnableApiInputAndOutputAuditing)
  176. auditLogInfo.CommitWithExeResult(executeResult);
  177. else
  178. auditLogInfo.CommitWithExeResult(null);
  179. }
  180. #endregion
  181. return executeResult;
  182. }
  183. catch (Exception exxx)
  184. {
  185. #region
  186. if (!this.config_DisableApiAuditing)
  187. {
  188. auditLogInfo.CommitWithExceptions(new Exception[] { exxx
  189. });
  190. }
  191. #endregion
  192. throw;
  193. }
  194. finally
  195. {
  196. #region
  197. if (!this.config_DisableApiAuditing)
  198. Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray());
  199. #endregion
  200. }
  201. }
  202. /// <summary>
  203. /// Invoke the Get method for the property.
  204. /// </summary>
  205. /// <param name="processor"></param>
  206. /// <param name="apiPropertyName"></param>
  207. /// <returns>the value of the Property</returns>
  208. public object InvokeUniversalApiPropertyGet(IProcessor processor,
  209. string apiPropertyName,
  210. RequestContext requestContext)
  211. {
  212. var pDescriptor = processor.ProcessorDescriptor();
  213. #region
  214. AuditLogInfo auditLogInfo = null;
  215. if (!this.config_DisableApiAuditing)
  216. {
  217. auditLogInfo = new AuditLogInfo()
  218. {
  219. ClientIdentity = requestContext.ClientIdentity,
  220. DeviceHandlerOrAppName = pDescriptor.DeviceHandlerOrApp.ToString(),
  221. ExecutionTime = DateTime.Now,
  222. };
  223. auditLogInfo.Actions.Add(
  224. new AuditLogActionInfo()
  225. {
  226. ApiName = "property : " + apiPropertyName,
  227. InputParameters = null,
  228. });
  229. auditLogInfo.Prepare();
  230. //logging early for knowing the calling is started
  231. this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray();
  232. }
  233. #endregion
  234. try
  235. {
  236. var targetPropertyInfo = pDescriptor.UniversalApiInfos.Where(apiInfo =>
  237. (apiInfo.PropertyApiInfo?.Name ?? "") == apiPropertyName)
  238. .FirstOrDefault()?.PropertyApiInfo;
  239. if (targetPropertyInfo == null)
  240. throw new EntryPointNotFoundException("Could not find Universal Api Property with name: " + (targetPropertyInfo.Name ?? ""));
  241. if (!targetPropertyInfo.CanRead
  242. || targetPropertyInfo.GetMethod == null
  243. || (targetPropertyInfo.GetMethod?.IsPrivate ?? false))
  244. {
  245. throw new EntryPointNotFoundException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "") + " does not allow Get");
  246. }
  247. try
  248. {
  249. /* indicates 'get' operation*/
  250. dynamic executeResult = null;
  251. if (pDescriptor.ProcessorType == ProcessorType.Application)
  252. executeResult = targetPropertyInfo.GetGetMethod().Invoke(processor, null);
  253. else if (pDescriptor.ProcessorType == ProcessorType.DeviceProcessor)
  254. executeResult = targetPropertyInfo.GetGetMethod().Invoke(pDescriptor.DeviceHandlerOrApp, null);
  255. #region
  256. if (!this.config_DisableApiAuditing)
  257. {
  258. /* logging again as the call has been done*/
  259. if (this.config_EnableApiInputAndOutputAuditing)
  260. auditLogInfo.CommitWithExeResult(executeResult);
  261. else
  262. auditLogInfo.CommitWithExeResult(null);
  263. }
  264. #endregion
  265. return executeResult;
  266. }
  267. catch (Exception exx)
  268. {
  269. throw;// new InvalidOperationException("Invoke UniversalApi Property: " + apiPropertyName + " exceptioned, detail: " + exx.ToString());
  270. }
  271. }
  272. catch (Exception exxx)
  273. {
  274. #region
  275. if (!this.config_DisableApiAuditing)
  276. {
  277. auditLogInfo.CommitWithExceptions(new Exception[] { exxx });
  278. }
  279. #endregion
  280. throw;
  281. }
  282. finally
  283. {
  284. #region
  285. if (!this.config_DisableApiAuditing)
  286. Task.WaitAll(this.auditingStores.Select(s => s.SaveAsync(auditLogInfo)).ToArray());
  287. #endregion
  288. }
  289. }
  290. private async Task<object> InvokeUniversalApiServiceAsync(IProcessor processor,
  291. MethodInfo apiServiceInfo, object[] methodParameters,
  292. RequestContext requestContext)
  293. {
  294. var descriptor = processor.ProcessorDescriptor();
  295. var targetMethodInfo = descriptor.UniversalApiInfos.Where(apiInfo =>
  296. (apiInfo.ServiceApiInfo?.Name ?? "") == apiServiceInfo.Name)
  297. .FirstOrDefault()?.ServiceApiInfo;
  298. if (targetMethodInfo == null)
  299. throw new EntryPointNotFoundException("Could not find Universal Api Method with name: " + (apiServiceInfo.Name ?? ""));
  300. try
  301. {
  302. dynamic result = null;
  303. if (descriptor.ProcessorType == ProcessorType.Application)
  304. result = await (dynamic)targetMethodInfo.Invoke(processor, methodParameters);
  305. else if (descriptor.ProcessorType == ProcessorType.DeviceProcessor)
  306. result = await (dynamic)targetMethodInfo.Invoke(descriptor.DeviceHandlerOrApp, methodParameters);
  307. return result;
  308. }
  309. catch (Exception exx)
  310. {
  311. throw;// new InvalidOperationException("Invoke UniversalApi Service: " + apiServiceInfo.Name + " exceptioned, detail: " + exx.ToString());
  312. }
  313. }
  314. /// <summary>
  315. /// Invoke the Set method for the property, if set value successfully, will return true, otherwise false.
  316. /// </summary>
  317. /// <param name="processor"></param>
  318. /// <param name="apiPropertyInfo"></param>
  319. /// <param name="propertySetValue">the value will be set to Property</param>
  320. /// <returns>if set value successfully, will return true, otherwise false.</returns>
  321. private bool InvokeUniversalApiPropertySet(IProcessor processor,
  322. PropertyInfo apiPropertyInfo, object propertySetValue,
  323. RequestContext requestContext)
  324. {
  325. var descriptor = processor.ProcessorDescriptor();
  326. var targetPropertyInfo = descriptor.UniversalApiInfos.Where(apiInfo =>
  327. (apiInfo.PropertyApiInfo?.Name ?? "") == apiPropertyInfo.Name)
  328. .FirstOrDefault()?.PropertyApiInfo;
  329. if (targetPropertyInfo == null)
  330. throw new EntryPointNotFoundException("Could not find Universal Api Property with name: " + (targetPropertyInfo.Name ?? ""));
  331. if (!targetPropertyInfo.CanWrite
  332. || targetPropertyInfo.SetMethod == null
  333. || (targetPropertyInfo.SetMethod?.IsPrivate ?? false))
  334. {
  335. throw new EntryPointNotFoundException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "") + " does not allow Set");
  336. }
  337. if (propertySetValue == null
  338. && !targetPropertyInfo.PropertyType.IsClass
  339. && !(targetPropertyInfo.PropertyType.IsGenericType && targetPropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)))
  340. throw new ArgumentException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "")
  341. + " does not compatible has its Set type(" + targetPropertyInfo.PropertyType + ") to Null");
  342. if (propertySetValue != null && !targetPropertyInfo.PropertyType.IsAssignableFrom(propertySetValue?.GetType()))
  343. throw new ArgumentException("Universal Api Property with name: " + (targetPropertyInfo.Name ?? "")
  344. + " does not compatible has its Set type(" + targetPropertyInfo.PropertyType + ") to " + (propertySetValue?.GetType().Name ?? "null"));
  345. try
  346. {
  347. /* indicates 'set' operation*/
  348. if (descriptor.ProcessorType == ProcessorType.Application)
  349. targetPropertyInfo.SetValue(processor, propertySetValue);
  350. else if (descriptor.ProcessorType == ProcessorType.DeviceProcessor)
  351. targetPropertyInfo.SetValue(descriptor.DeviceHandlerOrApp, propertySetValue);
  352. return true;
  353. }
  354. catch (Exception exx)
  355. {
  356. throw;// new InvalidOperationException("Invoke UniversalApi Property: " + apiPropertyInfo.Name + " exceptioned, detail: " + exx.ToString());
  357. }
  358. }
  359. }
  360. }