using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Matrix42.Common;
using Matrix42.Contracts.Common.Security;
using Matrix42.Contracts.ServiceManagement.ServiceContracts;
using Matrix42.Hosting.Contracts;
using Matrix42.Pandora.Contracts;
using Matrix42.Persistence.Contracts;
using Matrix42.Services.Description.Contracts;
using Matrix42.WebApi.Contracts;
using Matrix42.WebApi.Contracts.OData;
using update4u.SPS.Utility.GlobalConfiguration;
using C4IT.F4SDM;
using C4IT.FASD.Base;
using C4IT.Logging;
using static C4IT.FASD.Base.cF4SDTicket;
using static C4IT.Logging.cLogManager;
namespace C4IT.F4SD
{
[RoutePrefix("api/C4ITF4SDWebApi")]
public partial class F4SDM42WebApiController : ApiController
{
public static bool IsInitialized { get; private set; } = false;
public static F4SDM42WebApiController defaultInstance;
//private readonly IIncidentService _incidentService;
internal readonly GlobalConfigurationProvider _globalConfigurationProvider;
//public readonly IObjectService _objectService;
//public readonly IFragmentService _fragmentService;
private readonly IDependencyResolver _resolver;
private readonly IEnumerationProvider _enumerationProvider;
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 = "{2}";
public string BaseUrl => Request?.RequestUri == null ? string.Empty : $"{Request.RequestUri.Scheme}://{Request.RequestUri.Host}";
public string EndpointBaseUrl => $"{BaseUrl}/m42Services/api/c4itf4sdwebapi";
public F4SDM42WebApiController(IDependencyResolver resolver, IEnumerationProvider enumerationProvider)
{
_resolver = resolver ?? throw new ArgumentNullException(nameof(resolver));
_enumerationProvider = enumerationProvider ?? throw new ArgumentNullException(nameof(enumerationProvider));
defaultInstance = this;
//_objectService = objectService;
//_fragmentService = fragmentService;
//_incidentService = Guard.NullArgument(incidentService, "incidentService");
_globalConfigurationProvider = GlobalConfigurationProvider.Instance;
_f4stHelperService = new F4SDHelperService();
EnsureInitialized();
}
private static readonly object initLock = new object();
private static void EnsureInitialized()
{
try
{
//System.Diagnostics.Debugger.Launch();
lock (initLock)
{
if (IsInitialized || F4SDM42LogsWebApiController.IsInitialized)
return;
var Ass = Assembly.GetExecutingAssembly();
var LM = cLogManagerFile.CreateInstance(LocalMachine: true, A: Ass);
var CM = MethodBase.GetCurrentMethod();
LogMethodBegin(CM);
cLogManager.DefaultLogger.LogAssemblyInfo(Ass);
IsInitialized = true;
LogMethodEnd(CM);
}
}
catch { };
}
private T GetRequiredService() where T : class
{
var service = _resolver.TryGet();
if (service != null)
return service;
throw new InvalidOperationException($"Required Matrix42 service is not registered: {typeof(T).FullName}");
}
internal IJournalService GetJournalService()
{
return GetRequiredService();
}
internal object GetJournalServiceInstance()
{
try
{
return GetRequiredService();
}
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();
((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 getDirectLinkCreateTicket(string sid = "", string assetname = "")
{
await Task.Delay(0);
return await _f4stHelperService.getDirectLinkCreateTicket(sid, assetname);
}
[Route("getDirectLinkF4SD"), HttpGet]
public async Task getDirectLinkF4SD(Guid EOID, string Type)
{
return await _f4stHelperService.getDirectLinkF4SD(EOID, Type);
}
[Route("getTicketList"), HttpGet]
public async Task> getTicketList(
string sid,
int hours,
int queueoption = 0,
string queues = ""
)
{
var decodedPairs = ParseQueues(queues);
// Nun weiterreichen an Service
return await _f4stHelperService.getTicketListByUser(
sid,
hours,
queueoption,
decodedPairs
);
}
[Route("getTicketDetails"), HttpGet]
public async Task getTicketDetails(Guid objectId)
{
var tickets = await _f4stHelperService.getTicketDetails(new List() { objectId });
if (tickets.Count > 0)
return tickets[0];
else
return null;
}
[Route("getTicketHistory"), HttpGet]
public async Task> getTicketHistory(Guid objectId)
{
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)
{
return CreateMissingMethodDiagnostics("getTicketHistory controller", objectId, ex);
}
}
private async Task> 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 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(responseText);
}
catch
{
var token = Newtonsoft.Json.Linq.JToken.Parse(responseText);
if (token.Type == Newtonsoft.Json.Linq.JTokenType.Array)
return token.ToObject();
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();
}
return null;
}
}
internal static List CreateMissingMethodDiagnostics(string stage, Guid objectId, MissingMethodException ex)
{
var details = BuildMissingMethodDiagnostic(stage, objectId, ex);
LogEntry(details, LogLevels.Warning);
return new List
{
new cTicketJournalItem
{
ActivityObjectId = objectId,
CreatedBy = "C4ITF4SDM42WebApi",
CreationDate = DateTime.Now,
Header = "MissingMethodException diagnostic",
Description = details,
DescriptionHtml = WebUtility.HtmlEncode(details).Replace(Environment.NewLine, "
"),
IsVisibleForUser = false
}
};
}
private static string BuildMissingMethodDiagnostic(string stage, Guid objectId, MissingMethodException ex)
{
var builder = new StringBuilder();
builder.AppendLine($"Stage: {stage}");
builder.AppendLine($"ObjectId: {objectId}");
builder.AppendLine(ex.ToString());
builder.AppendLine("Loaded assemblies:");
var assemblyNames = new HashSet(StringComparer.OrdinalIgnoreCase)
{
"C4ITF4SDM42WebApi",
"C4ITF4SDM42WebApiHelper",
"Matrix42.Common",
"Matrix42.Common.Html",
"Matrix42.Contracts.Platform",
"Matrix42.Contracts.ServiceManagement",
"Matrix42.ServiceManager.BizLogic",
"Matrix42.BizLogic.Journal",
"Newtonsoft.Json",
"System.Net.Http.Formatting",
"update4u.SPS.DataLayer",
"update4u.SPS.Security"
};
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => assemblyNames.Contains(assembly.GetName().Name))
.OrderBy(assembly => assembly.GetName().Name))
{
builder.AppendLine($"{assembly.GetName().Name}, Version={assembly.GetName().Version}, Location={GetAssemblyLocation(assembly)}");
}
return builder.ToString();
}
private static string GetAssemblyLocation(Assembly assembly)
{
try
{
return assembly.Location;
}
catch
{
return string.Empty;
}
}
[Route("getTicketOverviewCounts"), HttpGet]
public async Task getTicketOverviewCounts(
string sid,
string scope = "personal",
string keys = "",
int queueoption = 0,
string queues = ""
)
{
var parsedKeys = (keys ?? string.Empty)
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(key => key.Trim())
.Where(key => !string.IsNullOrWhiteSpace(key))
.ToList();
var decodedQueues = ParseQueues(queues);
return await _f4stHelperService.getTicketOverviewCounts(sid, scope, parsedKeys, queueoption, decodedQueues);
}
[Route("getTicketOverviewCountsByRoles"), HttpPost]
public async Task getTicketOverviewCountsByRoles([FromBody] TicketOverviewCountsByRolesRequest request)
{
var parsedKeys = (request?.Keys ?? new List())
.Where(key => !string.IsNullOrWhiteSpace(key))
.Select(key => key.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var roleGuids = (request?.RoleGuids ?? new List())
.Where(roleId => roleId != Guid.Empty)
.Distinct()
.ToList();
var decodedQueues = ParseQueues(request?.Queues ?? string.Empty);
return await _f4stHelperService.getTicketOverviewCountsByRoles(
request?.Sid,
roleGuids,
parsedKeys,
request?.QueueOption ?? 0,
decodedQueues
);
}
[Route("getTicketOverviewRelations"), HttpGet]
public async Task> getTicketOverviewRelations(
string sid,
string scope = "personal",
string key = "",
int count = 0,
int queueoption = 0,
string queues = ""
)
{
var decodedQueues = ParseQueues(queues);
return await _f4stHelperService.getTicketOverviewRelations(sid, scope, key, count, queueoption, decodedQueues);
}
/*
[Route("updateActivitySolution/{objectId}"), HttpPost]
public async Task updateActivitySolution(Guid objectId, [FromBody] string SolutionHtml)
{
return new HttpResponseMessage
{
StatusCode = await _f4stHelperService.updateActivitySolution(objectId, SolutionHtml) ? HttpStatusCode.NoContent : HttpStatusCode.BadRequest,
};
}
*/
[Route("getPickup/{name}"), HttpGet]
//[CacheOutput(UseETAG = true)]
public async Task getPickup(string name, EntityEnumerationVisibilityMode mode = EntityEnumerationVisibilityMode.None, Int32 group = -1)
{
await Task.Delay(0);
EntityEnumeration enumerationTemp = GetEnumeration(name, mode);
var vals = enumerationTemp.Values;
if (group > -1)
{
vals = vals.Where(row => !row.Extentions.TryGetValue("StateGroup", out var stateGroup) || (ConvertHelper.ParseInt(stateGroup, 0) == group)).ToArray();
}
string[] columns = new string[] { "position" };
foreach (var item in vals)
{
item.Extentions = item.Extentions.Where(x => columns.Contains(x.Key.ToLower())).ToDictionary(x => x.Key, x => x.Value);
}
EntityEnumeration enumeration = new EntityEnumeration
{
Name = enumerationTemp.Name,
Values = vals.ToArray()
};
//CacheOutputAttribute.RegisterResponseEtag($"enum_{enumeration.Name}_{(int)mode}", $"{enumeration.Name}_{(int)mode}", cultureInvariant: false, userInvariant: true, val);
return Request.CreateResponse(HttpStatusCode.OK, enumeration);
}
[Route("getMyRoleMemberships"), HttpGet]
public async Task