fix: simplify journal history service loading
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
<module assembly="Matrix42.Pandora.BizLogic" />
|
||||
<module assembly="Matrix42.Distributed.Redis.Cache" />
|
||||
<module assembly="Matrix42.StorageService.BizLogic" />
|
||||
<module assembly="Matrix42.MsTeamsNotification.BizLogic" />
|
||||
<module assembly="Matrix42.ServiceConnection.Persistence" />
|
||||
<module assembly="Matrix42.Auth.BizLogic" />
|
||||
<module assembly="Matrix42.ServiceConnection.BizLogic" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<M42ExtensionId>6673a25d-33d6-4072-91d9-abed4be1a966</M42ExtensionId>
|
||||
<M42AssemblyPattern>C4ITF4SD*.*</M42AssemblyPattern>
|
||||
<M42PackageVersion>1.4.0.31</M42PackageVersion>
|
||||
<M42PackageVersion>1.4.0.32</M42PackageVersion>
|
||||
<M42BuildPackage>true</M42BuildPackage>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -34,10 +34,6 @@
|
||||
<HintPath>M42Libraries\26.1\Matrix42.Contracts.ServiceManagement.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Matrix42.BizLogic.Journal">
|
||||
<HintPath>M42Libraries\26.1\Matrix42.BizLogic.Journal.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Matrix42.Hosting.Contracts">
|
||||
<HintPath>M42Libraries\26.1\Matrix42.Hosting.Contracts.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using C4IT.F4SDM;
|
||||
using C4IT.FASD.Base;
|
||||
using C4IT.Logging;
|
||||
using Matrix42.BizLogic.Common.Journal;
|
||||
using Matrix42.Common;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
@@ -1455,7 +1454,7 @@ namespace C4IT.F4SD
|
||||
|
||||
private static IReadOnlyList<Matrix42.Contracts.Platform.Data.JournalEntryInfo> LoadJournalEntries(Guid activityEOID)
|
||||
{
|
||||
var journalService = F4SDM42WebApiController.defaultInstance.GetJournalServiceInstance();
|
||||
var journalService = F4SDM42WebApiController.defaultInstance.GetJournalService();
|
||||
var serviceType = journalService.GetType();
|
||||
var objectLinkTemplate = "<a href=\"#\" class=\"mx-object-journal--link {0} {1}\">{2}</a>";
|
||||
var methods = serviceType
|
||||
@@ -1482,12 +1481,6 @@ namespace C4IT.F4SD
|
||||
}
|
||||
catch (TargetInvocationException ex) when (ex.InnerException != null)
|
||||
{
|
||||
if (IsStorageWebApiExplorerResolutionFailure(ex.InnerException))
|
||||
{
|
||||
LogEntry("JournalService.GetJournalList requires IStorageService in this Matrix42 build. Falling back to JournalManager.GetObjectEntries for sandbox compatibility.", LogLevels.Warning);
|
||||
return LoadJournalEntriesFromJournalManager(activityEOID, objectLinkTemplate);
|
||||
}
|
||||
|
||||
throw ex.InnerException;
|
||||
}
|
||||
}
|
||||
@@ -1495,38 +1488,6 @@ namespace C4IT.F4SD
|
||||
throw new MissingMethodException($"No compatible IJournalService.GetJournalList overload was found on '{serviceType.FullName}'. Available overloads: {string.Join("; ", methods.Select(FormatMethodSignature))}");
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Matrix42.Contracts.Platform.Data.JournalEntryInfo> LoadJournalEntriesFromJournalManager(Guid activityEOID, string objectLinkTemplate)
|
||||
{
|
||||
var entries = JournalManager.GetObjectEntries(
|
||||
activityEOID,
|
||||
false,
|
||||
null,
|
||||
0,
|
||||
50,
|
||||
false,
|
||||
objectLinkTemplate,
|
||||
120);
|
||||
|
||||
return entries
|
||||
.Select(item => new Matrix42.Contracts.Platform.Data.JournalEntryInfo
|
||||
{
|
||||
Id = item.Id,
|
||||
Creator = item.CreatorName,
|
||||
CreatedDate = item.CreatedDate,
|
||||
Header = item.Header,
|
||||
Text = item.Body ?? string.Empty,
|
||||
VisibleInPortal = item.VisibleInPortal
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static bool IsStorageWebApiExplorerResolutionFailure(Exception exception)
|
||||
{
|
||||
var details = exception.ToString();
|
||||
return details.Contains("Matrix42.StorageService.Contracts.IStorageService")
|
||||
&& details.Contains("System.Web.Http.Description.IApiExplorer");
|
||||
}
|
||||
|
||||
private static bool IsJournalReaderMethod(MethodInfo method)
|
||||
{
|
||||
var methodName = method.Name;
|
||||
|
||||
@@ -43,12 +43,6 @@ namespace C4IT.F4SD
|
||||
|
||||
|
||||
private readonly F4SDHelperService _f4stHelperService;
|
||||
private const string TeamsNotificationServiceTypeName = "Matrix42.Contracts.Platform.MsTeams.ITeamsNotificationService";
|
||||
private const string StorageServiceTypeName = "Matrix42.StorageService.Contracts.IStorageService";
|
||||
private const int JournalStart = 0;
|
||||
private const int JournalCount = 50;
|
||||
private const int JournalTimeOffset = 120;
|
||||
private const string JournalObjectLinkTemplate = "<a href=\"#\" class=\"mx-object-journal--link {0} {1}\">{2}</a>";
|
||||
public string BaseUrl => Request?.RequestUri == null ? string.Empty : $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}";
|
||||
public string EndpointBaseUrl => $"{BaseUrl}/m42Services/api/c4itf4sdwebapi";
|
||||
|
||||
@@ -102,165 +96,6 @@ namespace C4IT.F4SD
|
||||
return GetRequiredService<IJournalService>();
|
||||
}
|
||||
|
||||
internal object GetJournalServiceInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetRequiredService<IJournalService>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEntry($"IJournalService could not be resolved by the Matrix42 container. Falling back to direct JournalService construction. {ex.GetType().FullName}: {ex.Message}", LogLevels.Warning);
|
||||
return CreateJournalServiceInstance();
|
||||
}
|
||||
}
|
||||
|
||||
private object CreateJournalServiceInstance()
|
||||
{
|
||||
var journalServiceType = FindLoadedType("Matrix42.ServiceManager.BizLogic.Services.JournalService")
|
||||
?? Type.GetType("Matrix42.ServiceManager.BizLogic.Services.JournalService, Matrix42.ServiceManager.BizLogic", true);
|
||||
var constructor = journalServiceType
|
||||
.GetConstructors()
|
||||
.OrderByDescending(item => item.GetParameters().Length)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException($"No public constructor was found for {journalServiceType.FullName}.");
|
||||
|
||||
var parameters = constructor.GetParameters();
|
||||
var arguments = new object[parameters.Length];
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
arguments[i] = ResolveJournalServiceConstructorParameter(parameters[i]);
|
||||
|
||||
return constructor.Invoke(arguments);
|
||||
}
|
||||
|
||||
private object ResolveJournalServiceConstructorParameter(System.Reflection.ParameterInfo parameter)
|
||||
{
|
||||
if (parameter.ParameterType == typeof(IDependencyResolver))
|
||||
return CreateJournalDependencyResolver(_resolver);
|
||||
|
||||
if (parameter.ParameterType.FullName == TeamsNotificationServiceTypeName || parameter.ParameterType.FullName == StorageServiceTypeName)
|
||||
return CreateNoOpInterfaceProxy(parameter.ParameterType);
|
||||
|
||||
var service = _resolver.TryGet(parameter.ParameterType);
|
||||
if (service != null)
|
||||
return service;
|
||||
|
||||
throw new InvalidOperationException($"Required JournalService constructor dependency is not registered: {parameter.ParameterType.FullName} ({parameter.Name}).");
|
||||
}
|
||||
|
||||
private static IDependencyResolver CreateJournalDependencyResolver(IDependencyResolver resolver)
|
||||
{
|
||||
var proxy = DispatchProxy.Create<IDependencyResolver, JournalDependencyResolverProxy>();
|
||||
((JournalDependencyResolverProxy)(object)proxy).Inner = resolver;
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private static Type FindLoadedType(string fullName)
|
||||
{
|
||||
return AppDomain.CurrentDomain
|
||||
.GetAssemblies()
|
||||
.Select(assembly => assembly.GetType(fullName, false))
|
||||
.FirstOrDefault(type => type != null);
|
||||
}
|
||||
|
||||
private static object CreateNoOpInterfaceProxy(Type interfaceType)
|
||||
{
|
||||
var createMethod = typeof(DispatchProxy)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Single(method => method.Name == nameof(DispatchProxy.Create) && method.IsGenericMethodDefinition);
|
||||
|
||||
return createMethod
|
||||
.MakeGenericMethod(interfaceType, typeof(NoOpInterfaceProxy))
|
||||
.Invoke(null, null);
|
||||
}
|
||||
|
||||
public class JournalDependencyResolverProxy : DispatchProxy
|
||||
{
|
||||
public IDependencyResolver Inner { get; set; }
|
||||
|
||||
protected override object Invoke(MethodInfo targetMethod, object[] args)
|
||||
{
|
||||
var requestedType = GetRequestedServiceType(targetMethod, args);
|
||||
if (requestedType?.FullName == StorageServiceTypeName)
|
||||
return CreateResolverResultForStorageService(targetMethod, requestedType);
|
||||
|
||||
try
|
||||
{
|
||||
return targetMethod.Invoke(Inner, args);
|
||||
}
|
||||
catch (TargetInvocationException ex) when (ex.InnerException != null)
|
||||
{
|
||||
throw ex.InnerException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Type GetRequestedServiceType(MethodInfo targetMethod, object[] args)
|
||||
{
|
||||
if (targetMethod.IsGenericMethod && targetMethod.GetGenericArguments().Length > 0)
|
||||
return targetMethod.GetGenericArguments()[0];
|
||||
|
||||
if (args?.Length > 0 && args[0] is Type type)
|
||||
return type;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static object CreateResolverResultForStorageService(MethodInfo targetMethod, Type requestedType)
|
||||
{
|
||||
if (targetMethod.Name.StartsWith("IsRegistered", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (targetMethod.Name.Contains("All"))
|
||||
return CreateEmptyEnumerable(targetMethod.ReturnType, requestedType);
|
||||
|
||||
return CreateNoOpInterfaceProxy(requestedType);
|
||||
}
|
||||
|
||||
private static object CreateEmptyEnumerable(Type returnType, Type itemType)
|
||||
{
|
||||
if (returnType.IsGenericType)
|
||||
return Array.CreateInstance(returnType.GetGenericArguments()[0], 0);
|
||||
|
||||
return Array.CreateInstance(itemType ?? typeof(object), 0);
|
||||
}
|
||||
|
||||
public class NoOpInterfaceProxy : DispatchProxy
|
||||
{
|
||||
public NoOpInterfaceProxy()
|
||||
{
|
||||
}
|
||||
|
||||
protected override object Invoke(MethodInfo targetMethod, object[] args)
|
||||
{
|
||||
return GetDefaultValue(targetMethod?.ReturnType);
|
||||
}
|
||||
}
|
||||
|
||||
private static object GetDefaultValue(Type returnType)
|
||||
{
|
||||
if (returnType == null || returnType == typeof(void))
|
||||
return null;
|
||||
|
||||
if (returnType == typeof(Task))
|
||||
return Task.CompletedTask;
|
||||
|
||||
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
var resultType = returnType.GetGenericArguments()[0];
|
||||
var result = resultType.IsValueType ? Activator.CreateInstance(resultType) : null;
|
||||
return typeof(Task)
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||
.Single(method => method.Name == nameof(Task.FromResult) && method.IsGenericMethodDefinition)
|
||||
.MakeGenericMethod(resultType)
|
||||
.Invoke(null, new[] { result });
|
||||
}
|
||||
|
||||
return returnType.IsValueType ? Activator.CreateInstance(returnType) : null;
|
||||
}
|
||||
|
||||
[Route("getDirectLinkCreateTicket"), HttpGet]
|
||||
public async Task<F4SDHelperService.DirectLink> getDirectLinkCreateTicket(string sid = "", string assetname = "")
|
||||
{
|
||||
@@ -308,13 +143,6 @@ namespace C4IT.F4SD
|
||||
{
|
||||
try
|
||||
{
|
||||
var journalPortalEntries = await TryGetJournalPortalEntries(objectId);
|
||||
if (journalPortalEntries != null)
|
||||
{
|
||||
LogEntry($"{journalPortalEntries.Count} Journal entries loaded from Matrix42 JournalPortalController for ObjectID={objectId}", LogLevels.Debug);
|
||||
return F4SDHelperService.CreateTicketJournalItems(objectId, journalPortalEntries);
|
||||
}
|
||||
|
||||
return await _f4stHelperService.GetJournalEntries(objectId);
|
||||
}
|
||||
catch (MissingMethodException ex)
|
||||
@@ -323,90 +151,6 @@ namespace C4IT.F4SD
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<Matrix42.Contracts.Platform.Data.JournalEntryInfo>> TryGetJournalPortalEntries(Guid objectId)
|
||||
{
|
||||
if (Request?.RequestUri == null)
|
||||
return null;
|
||||
|
||||
foreach (var requestUri in BuildJournalPortalUris(objectId))
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var handler = new HttpClientHandler { UseDefaultCredentials = true, AutomaticDecompression = DecompressionMethods.All })
|
||||
using (var client = new HttpClient(handler))
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
|
||||
{
|
||||
client.Timeout = TimeSpan.FromSeconds(15);
|
||||
CopyIncomingHeaders(request);
|
||||
using (var response = await client.SendAsync(request))
|
||||
{
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
LogEntry($"Matrix42 JournalPortalController returned {(int)response.StatusCode} {response.ReasonPhrase} for {requestUri}. {responseText}", LogLevels.Warning);
|
||||
continue;
|
||||
}
|
||||
|
||||
var entries = DeserializeJournalPortalEntries(responseText);
|
||||
if (entries != null)
|
||||
return entries;
|
||||
|
||||
LogEntry($"Matrix42 JournalPortalController response could not be deserialized for {requestUri}. {responseText}", LogLevels.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogEntry($"Matrix42 JournalPortalController request failed for {requestUri}. {ex.GetType().FullName}: {ex.Message}", LogLevels.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IEnumerable<Uri> BuildJournalPortalUris(Guid objectId)
|
||||
{
|
||||
var query = $"objectId={Uri.EscapeDataString(objectId.ToString())}&start={JournalStart}&count={JournalCount}&objectLinkTemplate={Uri.EscapeDataString(JournalObjectLinkTemplate)}&timeOffset={JournalTimeOffset}";
|
||||
yield return new Uri($"{BaseUrl}/m42Services/api/journalPortal?{query}");
|
||||
yield return new Uri($"{BaseUrl}/api/journalPortal?{query}");
|
||||
}
|
||||
|
||||
private void CopyIncomingHeaders(HttpRequestMessage request)
|
||||
{
|
||||
foreach (var header in Request.Headers)
|
||||
{
|
||||
if (string.Equals(header.Key, "Host", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(header.Key, "Connection", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(header.Key, "Accept-Encoding", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
request.Headers.TryAddWithoutValidation(header.Key, header.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private static Matrix42.Contracts.Platform.Data.JournalEntryInfo[] DeserializeJournalPortalEntries(string responseText)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Newtonsoft.Json.JsonConvert.DeserializeObject<Matrix42.Contracts.Platform.Data.JournalEntryInfo[]>(responseText);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var token = Newtonsoft.Json.Linq.JToken.Parse(responseText);
|
||||
if (token.Type == Newtonsoft.Json.Linq.JTokenType.Array)
|
||||
return token.ToObject<Matrix42.Contracts.Platform.Data.JournalEntryInfo[]>();
|
||||
|
||||
foreach (var propertyName in new[] { "result", "Result", "value", "Value" })
|
||||
{
|
||||
var resultToken = token[propertyName];
|
||||
if (resultToken != null && resultToken.Type != Newtonsoft.Json.Linq.JTokenType.Null)
|
||||
return resultToken.ToObject<Matrix42.Contracts.Platform.Data.JournalEntryInfo[]>();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static List<cTicketJournalItem> CreateMissingMethodDiagnostics(string stage, Guid objectId, MissingMethodException ex)
|
||||
{
|
||||
var details = BuildMissingMethodDiagnostic(stage, objectId, ex);
|
||||
|
||||
Reference in New Issue
Block a user