Skip to content

Commit

Permalink
Merge pull request #7 from TrueAnalyticsSolutions/tbm0115/GenericAdap…
Browse files Browse the repository at this point in the history
…terService

Tbm0115/generic adapter service
  • Loading branch information
jmurphy2973 authored Jan 25, 2023
2 parents 33db46b + c39499a commit eb417e0
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 178 deletions.
10 changes: 7 additions & 3 deletions AdapterInterface/AdapterExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ private static PropertyInfo[] GetDataItemProperties(Type type)
private static Dictionary<Type, PropertyInfo[]> _dataItemProperties = new Dictionary<Type, PropertyInfo[]>();
private static bool TryAddDataItems(this Adapter adapter, Type dataModelType, string dataItemNamePrefix = "", string dataItemDescriptionPrefix = "")
{
if (_dataItemProperties.TryGetValue(dataModelType, out PropertyInfo[] dataItemProperties)) return true;
if (!_dataItemProperties.TryGetValue(dataModelType, out PropertyInfo[] dataItemProperties))
{
dataItemProperties = GetDataItemProperties(dataModelType);
_dataItemProperties.Add(dataModelType, dataItemProperties);
}

dataItemProperties = GetDataItemProperties(dataModelType);
_dataItemProperties.Add(dataModelType, dataItemProperties);
bool allDataItemsAdded = true;


Expand All @@ -57,6 +59,8 @@ private static bool TryAddDataItems(this Adapter adapter, Type dataModelType, st
string dataItemName = dataItemNamePrefix + dataItemAttribute.Name;
string dataItemDescription = dataItemDescriptionPrefix + dataItemAttribute.Description;

if (adapter.Contains(dataItemName)) continue;

switch (dataItemAttribute)
{
case DataItemPartialAttribute _:
Expand Down
2 changes: 1 addition & 1 deletion AdapterInterface/AdapterInterface.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<ApplicationIcon>icon.ico</ApplicationIcon>
<PackageProjectUrl>https://github.com/TrueAnalyticsSolutions/MtconnectCore.Adapter</PackageProjectUrl>
<RepositoryUrl>$(ProjectUrl)</RepositoryUrl>
<Version>1.0.10-alpha</Version>
<Version>1.0.11-alpha</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
202 changes: 202 additions & 0 deletions Service/AdapterFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
using Mtconnect;
using Mtconnect.AdapterInterface;
using Service.Configuration;
using System.Reflection;

namespace Service
{
/// <summary>
/// A factory that discovers and creates <see cref="AdapterInstance"/> from the <see cref="ServiceConfiguration"/>.
/// </summary>
public class AdapterFactory
{
private List<AdapterInstance> _adapters { get; set; } = new List<AdapterInstance>();
private ILogger<Worker>? _logger { get; set; }

public AdapterFactory(ILogger<Worker>? logger = null)
{
_logger = logger;
}

/// <summary>
/// Adds a <see cref="AdapterInstance"/> for the given <paramref name="adapter"/>.
/// </summary>
/// <param name="adapter">Instance of an <see cref="Adapter"/> to create and add an <see cref="AdapterInstance"/>.</param>
public void Add(Adapter adapter)
{
if (adapter == null)
{
var nullEx = new ArgumentNullException(nameof(adapter));
_logger?.LogError(nullEx, nullEx.Message);
return;
}

_adapters.Add(new AdapterInstance(adapter));
}

/// <summary>
/// Adds the <paramref name="source"/> to each <see cref="Adapter"/> of type <paramref name="adapterType"/> that references the <see cref="IAdapterSource"/> in the <see cref="ServiceConfiguration"/>.
/// </summary>
/// <param name="source"></param>
/// <param name="adapterType"></param>
public void Add(IAdapterSource source, Type adapterType)
{
if (source == null)
{
var nullEx = new ArgumentNullException(nameof(source));
_logger?.LogError(nullEx, nullEx.Message);
return;
} else if (adapterType == null)
{
var nullEx = new ArgumentNullException(nameof(adapterType));
_logger?.LogError(nullEx, nullEx.Message);
return;
}

var adapters = _adapters.Where(o => o.Instance.GetType() == adapterType);

if (!adapters.Any())
{
_logger?.LogWarning("No Adapter found of Type '{AdapterType}'", adapterType?.FullName);
return;
}

foreach (var adapter in _adapters)
{
adapter.Sources.Add(source);
}
}

/// <summary>
/// Starts all <see cref="Adapter"/>s.
/// </summary>
/// <param name="token">Reference to the upper-level cancellation token that can cancel the execution of each <see cref="Adapter"/>.</param>
public void Start(CancellationToken token = default)
{
if (!_adapters.Any())
{
var ex = new InvalidOperationException("There are no Adapters to start");
_logger?.LogWarning(ex, ex.Message);
return;
}

foreach (var adapterInstance in _adapters)
{
var adapterType = adapterInstance.Instance.GetType();
if (!adapterInstance.Sources.Any())
{
var ex = new InvalidOperationException("There are no IAdapterSources to start");
_logger?.LogWarning(ex, ex.Message + " in Adapter type '{AdapterType}'", adapterType.FullName);
continue;
}
adapterInstance.Instance.Start(adapterInstance.Sources, token: token);
_logger?.LogInformation("Started Adapter {AdapterType}", adapterType.FullName);
}
}

/// <summary>
/// Stops all <see cref="Adapter"/>s.
/// </summary>
public void Stop()
{
if (!_adapters.Any())
{
var ex = new InvalidOperationException("There are no Adapters to stop");
_logger?.LogWarning(ex, ex.Message);
return;
}

foreach (var adapterInstance in _adapters)
{
var adapterType = adapterInstance.Instance.GetType();
if (adapterInstance.Instance.State <= 0)
{
var ex = new InvalidOperationException("Not necessary to stop an Adapter that is already stopped");
_logger?.LogWarning(ex, ex.Message + ". Adapter type '{AdapterType}' was '{AdapterState}'", adapterType, adapterInstance.Instance.State);
continue;
}

adapterInstance.Instance.Stop();
}
}

/// <summary>
/// Creates a new instance of <see cref="AdapterFactory"/> from the loaded assemblies imported from the <paramref name="config"/>.
/// </summary>
/// <param name="dlls">Reference to the list of imported assemblies.</param>
/// <param name="config">Reference to the loaded <see cref="ServiceConfiguration"/>.</param>
/// <param name="logger">Reference to the injected logger for the service.</param>
/// <param name="adapterLogger">Reference to the injected logger for all Adapters.</param>
/// <returns></returns>
public static AdapterFactory CreateFromTypes(IEnumerable<Assembly> dlls, ServiceConfiguration config, ILogger<Worker>? logger = null, ILogger<Adapter>? adapterLogger = null)
{
var factory = new AdapterFactory(logger);

foreach (var adapterConfig in config.Adapters)
{
var adapterType = dlls
.Select(o => o.GetType(adapterConfig.Adapter))
.Where(o => o != null)
.FirstOrDefault();

//AdapterOptions result, ILogger<Adapter> workerLogger = null
var adapterCtors = adapterType.GetConstructors();
foreach (var adapterCtor in adapterCtors)
{
var optionsParam = adapterCtor.GetParameters().FirstOrDefault(o => o.ParameterType.IsSubclassOf(typeof(AdapterOptions)));
object? options = ReflectionExtensions.ConstructFromConfig(adapterConfig.Options, optionsParam?.ParameterType, logger);

var adapter = adapterCtor.Invoke(new object?[] { options, adapterLogger }) as Adapter;
if (adapter == null)
{
logger?.LogError(new InvalidCastException("Failed to construct Adapter"), "Failed to construct Adapter { AdapterType }", adapterType.FullName);
continue;
}

factory.Add(adapter);
}
}

_addSources(factory, dlls, config, logger);

return factory;
}

/// <summary>
/// Adds all <see cref="IAdapterSource"/>s from the imported DLLs and assigns them to the appropriate <see cref="Adapter"/>s.
/// </summary>
/// <param name="factory">Reference to the <see cref="AdapterFactory"/> to load the <see cref="IAdapterSource"/>s into.</param>
/// <param name="dlls">Reference to the list of imported assemblies.</param>
/// <param name="config">Reference to the loaded <see cref="ServiceConfiguration"/>.</param>
/// <param name="logger">Reference to the injected logger for the service.</param>
private static void _addSources(AdapterFactory factory, IEnumerable<Assembly> dlls, ServiceConfiguration config, ILogger<Worker>? logger = null)
{
foreach (var dll in dlls)
{
var sourceTypes = dll.GetTypes().Where(o => typeof(IAdapterSource).IsAssignableFrom(o) && !o.IsInterface && !o.IsAbstract);

foreach (var sourceType in sourceTypes)
{
var sourceConfig = config.Sources?.FirstOrDefault(o => o.Source == sourceType.Name || o.Source == sourceType.FullName);
IAdapterSource? source = ReflectionExtensions.ConstructFromConfig(sourceConfig?.Options, sourceType, logger) as IAdapterSource;
if (source == null)
{
logger?.LogError(new InvalidCastException("Failed to construct IAdapterSource"), "Failed to construct IAdapterSource { SourceType }", sourceType.FullName);
continue;
}

var adapterConfigs = config.Adapters.Where(o => o.Sources.Contains(sourceType.Name) || o.Sources.Contains(sourceType.FullName));
var adapterTypes = dlls
.SelectMany(o => adapterConfigs.Select(c => o.GetType(c.Adapter)))
.Where(o => o != null)
.Distinct()
.ToArray();
foreach (var adapterType in adapterTypes)
{
factory.Add(source, adapterType);
}
}
}
}
}
}
29 changes: 29 additions & 0 deletions Service/AdapterInstance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Mtconnect;

namespace Service
{
/// <summary>
/// Wrapper for managing the <see cref="Adapter"/> that must be started and the relevant <see cref="IAdapterSource" /> to assign to it.
/// </summary>
public class AdapterInstance
{
/// <summary>
/// Reference to the <see cref="Adapter"/> implementation that must be started.
/// </summary>
public Adapter Instance { get; set; }

/// <summary>
/// Reference to the <see cref="IAdapterSource"/> implementation that must be assigned to <see cref="Instance"/>.
/// </summary>
public List<IAdapterSource> Sources { get; set; } = new List<IAdapterSource>();

/// <summary>
/// Constructs a new <see cref="AdapterInstance"/> from a constructed <see cref="Adapter"/>.
/// </summary>
/// <param name="instance">Reference to a previously constructed <see cref="Adapter"/>.</param>
public AdapterInstance(Adapter instance)
{
Instance = instance;
}
}
}
2 changes: 2 additions & 0 deletions Service/Imports/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Universal MTConnect Adapter Service Imports
Use this folder to import libraries implementing `Mtconnect.Adapter` and `Mtconnect.IAdapterSource`.
85 changes: 85 additions & 0 deletions Service/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Collections;

namespace Service
{
internal static class ReflectionExtensions
{
internal static object? ConstructFromConfig(object? config, Type targetType, ILogger<Worker>? logger = null)
{
Type? configType = config?.GetType();
bool isDictionary = config is IDictionary<string, object?>;

object? result = null;
var ctors = targetType.GetConstructors().OrderByDescending(o => o.GetParameters()?.Length);
foreach (var ctor in ctors)
{
var ctorParams = ctor.GetParameters();

List<object?> resultParams = new List<object?>();
foreach (var ctorParam in ctorParams)
{
object? value = null;
if (isDictionary)
{
if ((config as IDictionary).Contains(ctorParam.Name))
{
value = (config as IDictionary)[ctorParam.Name];
}
}
else
{
var property = configType?.GetProperty(ctorParam.Name);
if (property != null)
{
value = property.GetValue(config, null);
}
}

if (value != null)
{
value = Convert.ChangeType(value, ctorParam.ParameterType);

resultParams.Add(value);
}
else if (ctorParam.IsOptional)
{
resultParams.Add(ctorParam.DefaultValue);
}
}
if (resultParams.Count == ctorParams.Length)
{
result = ctor.Invoke(resultParams.ToArray());
if (result != null) break;
}
}

if (result == null)
{
logger?.LogError("Could not construct {AdapterOptionsType}", targetType.FullName);
return null;
}

Dictionary<string, object?>? configProperties = null;
if (isDictionary)
{
configProperties = (config as Dictionary<string, object?>);
}
else
{
configProperties = configType?.GetProperties().ToDictionary(o => o.Name, o => o.GetValue(config, null));
}
if (configProperties != null)
{
foreach (var kvp in configProperties)
{
var property = targetType.GetProperty(kvp.Key);
if (property == null) continue;

property.SetValue(result, kvp.Value);
}
}

return result;
}
}
}
Loading

0 comments on commit eb417e0

Please sign in to comment.