-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathModule.cs
More file actions
346 lines (292 loc) · 17.2 KB
/
Module.cs
File metadata and controls
346 lines (292 loc) · 17.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/*
The MIT License (MIT)
Copyright (c) 2025 John Earnshaw, NetModules Foundation.
Repository Url: https://github.com/netmodules/netmodules/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
using System;
using System.Linq;
using NetModules.Interfaces;
using NetModules.Classes;
using NetModules.Events;
namespace NetModules
{
/// <summary>
/// A Module is an <see cref="IEventHandler"/> that can be instatiated by an <see cref="IModuleHost"/>.
/// This class exposes members to the <see cref="ModuleHost"/> that identify both the IModule itself and what <see cref="IEvent"/>
/// instances it can handle.
/// </summary>
[Serializable]
[Module(Description = "This is an abstract module that can be inherited while creating a new Module Type.")]
public abstract class Module : IModule
{
/// <summary>
/// The <see cref="IModuleHost"/> is responsible for loading known <see cref="IEvent"/> and known instances inheriting from <see cref="Module"/>.
/// If you wish to raise an <see cref="IEvent"/> for handling this should be done by invoking <see cref="IEventHandler.Handle(IEvent)"/> on
/// <see cref="IModuleHost"/>. Although individual <see cref="Module"/> instances can be accessed via <see cref="IModuleHost.Modules"/>, it is
/// not recommended to invoke <see cref="Module.Handle(IEvent)"/> directly as this will bypass internal processing and the <see cref="IEvent"/>
/// instance will be processed invisibly. This is called a ghost event.
/// </summary>
public virtual IModuleHost Host { get; internal set; }
/// <summary>
/// This contains module metadata such as the module's name and public description. This metadata is visible to other modules that are loaded
/// in the system and can be used for documentation and dependency checks.
/// </summary>
public virtual IModuleAttribute ModuleAttributes
{
get; internal set;
}
Uri path;
/// <summary>
/// Returns the system directory from where the module was loaded. This can be useful for loading additional resources
/// or creating files such as logs and storing other data alongside the module.
/// </summary>
public virtual Uri WorkingDirectory
{
get
{
if (path == null)
{
path = AssemblyTools.GetPathToAssembly(GetType());
}
return path;
}
}
/// <summary>
/// This property is set internally by <see cref="IModuleHost.Modules"/> once the module has been loaded and an instance
/// of the module has been created. If the module if then unloaded this property will be set back to false. This property
/// is checked before <see cref="IEventHandler.CanHandle(IEvent)"/> and <see cref="IEventHandler.Handle(IEvent)"/> are
/// invoked on <see cref="Host"/>. Only loaded modules can handle events.
/// </summary>
public virtual bool Loaded
{
get; internal set;
}
/// <summary>
/// CanHandle is invoked by <see cref="Host"/> to see if this <see cref="Module"/> instance is able to handle
/// the requested <see cref="IEvent"/> object.
/// </summary>
/// <param name="e">The <see cref="IEvent"/>instance to to check if this <see cref="Module"/> instance can handle it.</param>
/// <returns></returns>
public abstract bool CanHandle(IEvent e);
/// <summary>
/// This method is invoked by <see cref="IModuleHost"/> passing an <see cref="IEvent"/> instance to this <see cref="Module"/>
/// for further processing. Once the <see cref="IEvent.Handled"/> property is set to true and retrned, no further processing
/// will occur on the <see cref="IEvent"/> instance by other modules that may be able to handle the <see cref="IEvent"/> type.
/// </summary>
/// <param name="e">The <see cref="IEvent"/> instance to be handled by this <see cref="Module"/>.</param>
public abstract void Handle(IEvent e);
/// <summary>
/// Implemented due to demand. An object implementing <see cref="IModule"/> may often require the ability to retrieve configurable settings.
/// This method is implemented in <see cref="Module"/> and acts as a wrapper for raising a <see cref="GetSettingEvent"/> into the
/// <see cref="Host"/> for handling. If a <see cref="Module"/> is not loaded that can handle <see cref="GetSettingEvent"/> then this method
/// will fail and attempt to log using <see cref="Module.Log(LoggingEvent.Severity, object[])"/>. Functionality can be overridden.
/// </summary>
/// <typeparam name="T">The type of the required setting.</typeparam>
/// <param name="name">The identifier string for the required setting. This is passed to the generated <see cref="IEventInput"/>.</param>
/// <param name="default">The default setting to return if a configured setting is not available or the returned setting is the wrong type.</param>
public virtual T GetSetting<T>(string name, T @default = default) => GetSetting(name, @default, false);
/// <summary>
/// Implemented due to demand. An object implementing <see cref="IModule"/> may often require the ability to retrieve configurable settings.
/// This method is implemented in <see cref="Module"/> and acts as a wrapper for raising a <see cref="GetSettingEvent"/> into the
/// <see cref="Host"/> for handling. If a <see cref="Module"/> is not loaded that can handle <see cref="GetSettingEvent"/> then this method
/// will fail and attempt to log using <see cref="Module.Log(LoggingEvent.Severity, object[])"/>. Functionality can be overridden.
/// </summary>
/// <typeparam name="T">The type of the required setting.</typeparam>
/// <param name="name">The identifier string for the required setting. This is passed to the generated <see cref="IEventInput"/>.</param>
/// <param name="default">The default setting to return if a configured setting is not available or the returned setting is the wrong type.</param>
/// <param name="suppressLogMessage">Set to true if you do not wish to raise a <see cref="LoggingEvent"/> if the underlying <see cref="GetSettingEvent"/> is not marked as handled.</param>
public virtual T GetSetting<T>(string name, T @default = default, bool suppressLogMessage = false)
{
/*
* This overridable method acts is a wrapper for creating a GetSettingEvent and invoking IModuleHost.Handle on it. If no module exists to handle the
* GetSettingEvent or if the return type is unexpected we do some logging using the IModule.Log method, see below...
*/
var getSettingEvent = new GetSettingEvent();
getSettingEvent.Input = new GetSettingEventInput
{
ModuleName = ModuleAttributes.Name,
SettingName = name
};
Host.Handle(getSettingEvent);
if (getSettingEvent.Handled)
{
var setting = getSettingEvent.Output.Setting;
if (setting != null)
{
// We check that the setting type is correct before returning it.
if (setting is T s)
{
return s;
}
else if (setting is IConvertible c)
{
// If not, can the setting be converted??
// If the setting can't be the requested type, we log a message suggesting the type format of the setting so that the developer can
// request the setting in the correct format and use casting or an alternative conversion method to format the setting as required.
// An example here is that if the setting is parsed into a dictionary using JSON, the serializer may have parsed a numeric value
// into an incorrect value type. Where a double may be required, the setting may have been parsed into a float. In this case, the
// float setting must be requested using this method and then cast to a double after the setting value is retrieved.
var type = typeof(T);
try
{
// Quick TryParse for enum types if the setting contains a string representation. We can use @default to check for an enum type
// here since @default should always contain a default value in the typeof(T) for the requested setting.
if (@default is Enum && setting is string str
&& Enum.TryParse(type, str, true, out var pEnum))
{
return (T)pEnum;
}
return (T)c.ToType(type, System.Globalization.CultureInfo.InvariantCulture);
}
catch
{
// This has to be an invalid cast, so raise a LoggingEvent indefinitely...
Log(LoggingEvent.Severity.Warning,
new InvalidCastException(
string.Format(Constants._SettingTypeMismatch, setting.GetType(), type)),
getSettingEvent);
return @default;
}
}
}
return @default;
}
// The setting or a GetSettingEvent handler may not exist on purpose???
// If the event is not handled, we raise a LoggingEvent to inform the developer that no module exists to handle the event for the requested setting.
// This can be suppressed by passing the suppressLogMessage parameter as true, or by a handling module setting a boolean metadata value to true on the
// GetSettingEvent for the key "suppressLogMessage". This is useful for modules that may not be able to handle the event but do not want to log an error.
if (!suppressLogMessage || getSettingEvent.GetMeta(Constants._MetaSurpressLogMessage, false))
{
Log(LoggingEvent.Severity.Error
, string.Format(Constants._SettingNotFound, getSettingEvent.Name)
, getSettingEvent);
}
return @default;
}
/// <summary>
/// Implemented due to demand. An <see cref="IModule"/> may often require the ability to log debug data, errors, analytics or other information.
/// This method is implemented in <see cref="Module"/> and acts as a wrapper for raising a <see cref="LoggingEvent"/> to <see cref="IModuleHost"/>
/// for handling. If a <see cref="Module"/> is not loaded that can handle the <see cref="LoggingEvent"/> then this method will fail silently. This
/// Functionality can be overridden on a per-module basis.
/// </summary>
/// <param name="severity">The severity of logging required. This is passed to the generated LoggingEvent for handling.</param>
/// <param name="arguments">The arguments to be logged. These are passed to the generated <see cref="IEventInput"/>.</param>
public virtual void Log(LoggingEvent.Severity severity, params object[] arguments)
{
/*
* This overridable method creates a LoggingEvent, populates its properties and passes it to IModuleHost for handling.
* If a module does not exist to handle an IEvent of type LoggingEvent logging will fail silently. If we were to attempt
* logging of a failed LoggingEvent we would create an infinite loop of logging fails... I hear StackOverflowException.
*/
if (arguments != null && arguments.Length > 0)
{
// We insert the raising module name at index 0 of the arguments array, and raise a
// LoggingEvent on its behalf so that it can processed by any LoggingEvent event
// handlers.
var loggingEvent = new LoggingEvent
{
Input = new LoggingEventInput
{
Severity = severity,
Arguments = arguments.ToList()
}
};
loggingEvent.Input.Arguments.Insert(0, ModuleAttributes.Name);
Host.Handle(loggingEvent);
}
}
/// <summary>
/// This method is invoked by <see cref="IModuleHost"/> when the module is loading. Attempting raise an <see cref="IEvent"/> from the constructor
/// will fail and any <see cref="IEvent"/> shoud be raised here. Raising an <see cref="IEvent"/> here may fail if the required <see cref="Module"/>
/// is not yet loaded to handle the <see cref="IEvent"/>.
/// </summary>
public virtual void OnLoading()
{
// Empty method is designed to be overriden if required.
}
/// <summary>
/// This method is invoked by <see cref="IModuleHost"/> when the <see cref="Module"/> is loaded. Any cross-module activity such as raising an
/// <see cref="IEvent"/> to be handled via <see cref="IModuleHost"/> can proceed successfully here provided an <see cref="IModule"/> is loaded
/// to handle the <see cref="IEvent"/>.
/// </summary>
public virtual void OnLoaded()
{
// Empty method is designed to be overriden if required.
}
/// <summary>
/// This method is invoked by <see cref="IModuleHost"/> when all instances of <see cref="Module"/> have been loaded. Any cross-module activity such as raising an
/// <see cref="IEvent"/> to be handled via <see cref="IModuleHost"/> can proceed successfully here provided an <see cref="IModule"/> is loaded
/// to handle the <see cref="IEvent"/>.
/// </summary>
public virtual void OnAllModulesLoaded()
{
// Empty method is designed to be overriden if required.
}
/// <summary>
/// This method is invoked by <see cref="IModuleHost"/> before a <see cref="Module"/> is unloaded. Any per-module finalization and cleanup
/// should be done here.
/// </summary>
public virtual void OnUnloading()
{
// Empty method is designed to be overriden if required.
}
/// <summary>
/// This method is invoked by <see cref="IModuleHost"/> when the module is unloaded.
/// </summary>
public virtual void OnUnloaded()
{
// Empty method is designed to be overriden if required.
}
#region Overrides
/// <summary>
/// Returns the <see cref="ModuleAttribute.Name"/>
/// </summary>
public override string ToString()
{
return ModuleAttributes.Name;
}
/// <summary>
/// Checks to see if the objects are equal. If the comparing object is a <see cref="Module"/> then <see cref="ModuleAttribute.Name"/>
/// is used for comparison.
/// </summary>
public override bool Equals(object obj)
{
if (obj is Module module)
{
return module.ModuleAttributes.Name.Equals(ModuleAttributes.Name);
}
return base.Equals(obj);
}
/// <summary>
/// Returns the <see cref="ModuleAttribute.Name"/> HashCode for comparison.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return ModuleAttributes.Name.GetHashCode();
}
/// <summary>
/// Should we be able to implicitly convert a <see cref="Module"/> to a <see cref="string"/>?
/// </summary>
public static implicit operator string(Module m)
{
return m.ModuleAttributes.Name.ToString();
}
#endregion
}
}