forked from mtconnect/dot_net_sdk
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from TrueAnalyticsSolutions/tbm0115/GenericAdap…
…terService Tbm0115/generic adapter service
- Loading branch information
Showing
8 changed files
with
346 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.