commit fd109766a8067194f8c866251b53cba12cf2ee05 Author: Harald Wolff Date: Sat Aug 3 13:49:06 2019 +0200 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf793ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Autosave files +*~ + +# build +[Oo]bj/ +[Bb]in/ +packages/ +TestResults/ + +# globs +Makefile.in +*.DS_Store +*.sln.cache +*.suo +*.cache +*.pidb +*.userprefs +*.usertasks +config.log +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.user +*.tar.gz +tarballs/ +test-results/ +Thumbs.db +.vs/ + +# Mac bundle stuff +*.dmg +*.app + +# resharper +*_Resharper.* +*.Resharper + +# dotCover +*.dotCover diff --git a/Application.cs b/Application.cs new file mode 100644 index 0000000..d847e6f --- /dev/null +++ b/Application.cs @@ -0,0 +1,127 @@ +using System; +using ln.http; +using System.Collections.Generic; +using System.Collections; +using System.Linq; +using ln.logging; +using ln.types; +using System.Net; +using ln.application.service; +using ln.http.resources; + +namespace ln.application +{ + public class Application : IApplicationInterface + { + public ArgumentContainer Arguments { get; protected set; } + public HTTPServer HttpServer { get; private set; } + + public ServiceContainer ServiceContainer { get; } + public ResourceApplication HTTPApplication { get; private set; } + + public MemoryLogger MemoryLogger { get; } + + public Application() + { + MemoryLogger = new MemoryLogger(8192); + Logger.Default.Backends.Add(MemoryLogger); + + Arguments = new ArgumentContainer() + .Add((char)0, "log-level", "INFO") + .Add('l', "http-listen","0.0.0.0") + .Add('p', "http-port", "8080"); + + ServiceContainer = new ServiceContainer(this); + } + + public virtual void PrepareStart() + { + } + + public void Start(String[] arguments) + { + Arguments.Parse(arguments); + Logger.ConsoleLogger.MaxLogLevel = (LogLevel)Enum.Parse(typeof(LogLevel), Arguments["log-level"].Value); + + /* Startup Sequence */ + + Logging.Log(LogLevel.INFO, "Calling PrepareStart()"); + PrepareStart(); + + /* HTTP Server */ + Logging.Log(LogLevel.INFO, "Start: HTTPServer on {0}:{1}", Arguments['l'].Value, Arguments['p'].IntegerValue); + HttpServer = new HTTPServer(); + HttpServer.AddEndpoint(new System.Net.IPEndPoint( + IPAddress.Parse(Arguments['l'].Value), + Arguments['p'].IntegerValue + )); + + HttpServer.Start(); + HTTPApplication = new ResourceApplication(); + HttpServer.DefaultApplication = HTTPApplication; + + + /* Application Services */ + foreach (ServiceDefinition serviceDefinition in ServiceContainer) + { + if (serviceDefinition.AutoStart) + ServiceContainer.Start(serviceDefinition); + } + + } + public void Stop() + { + Logging.Log(LogLevel.INFO, "Application shutdown requested"); + + /* Application Services */ + foreach (ServiceDefinition serviceDefinition in ServiceContainer) + { + if (serviceDefinition.IsAlive) + { + ServiceContainer.Stop(serviceDefinition); + if (serviceDefinition.IsAlive) + throw new Exception("application service could not be stopped"); + } + } + + /* HTTP Server */ + Logging.Log(LogLevel.INFO, "Stopping HTTP server"); + HttpServer.Stop(); + } + + + + /* + * Plugin Classes + */ + Dictionary> pluginInstances = new Dictionary>(); + + public void RegisterPluginInstance(PC pluginInstance) => RegisterPluginInstance(typeof(PC),pluginInstance); + public void RegisterPluginInstance(Type type,object pluginInstance) + { + if (!pluginInstances.ContainsKey(type)) + pluginInstances.Add(type, new List()); + + if (!pluginInstances[type].Contains(pluginInstance)) + pluginInstances[type].Add(pluginInstance); + } + + public IEnumerable GetPluginInstances() + { + Type pluginType = typeof(PC); + if (!pluginInstances.ContainsKey(pluginType)) + return new PC[0]; + + return pluginInstances[pluginType].Select((ut) => (PC)ut); + } + public IEnumerable GetPluginInstances(Type pluginType) + { + if (!pluginInstances.ContainsKey(pluginType)) + return new object[0]; + + return pluginInstances[pluginType]; + } + + + } +} diff --git a/IApplicationInterface.cs b/IApplicationInterface.cs new file mode 100644 index 0000000..e5bdc31 --- /dev/null +++ b/IApplicationInterface.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using ln.application.service; +using ln.http.resources; +using ln.http; +using ln.types; +namespace ln.application +{ + public interface IApplicationInterface + { + ArgumentContainer Arguments { get; } + + HTTPServer HttpServer { get; } + ResourceApplication HTTPApplication { get; } + ServiceContainer ServiceContainer { get; } + + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c1026d6 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,26 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +[assembly: AssemblyTitle("ln.application")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. + +[assembly: AssemblyVersion("1.0.*")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] diff --git a/ln.application.csproj b/ln.application.csproj new file mode 100644 index 0000000..98c8579 --- /dev/null +++ b/ln.application.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + {44AA3A50-7214-47F2-9D60-6FF34C0FE6D3} + Library + ln.application + ln.application + v4.7 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + + + true + bin\Release + prompt + 4 + false + + + + + + + + + + + + + + + {8D9AB9A5-E513-4BA7-A450-534F6456BF28} + ln.types + + + {D471A566-9FB6-41B2-A777-3C32874ECD0E} + ln.logging + + + {F9086FE4-8925-42FF-A59C-607341604293} + ln.http.resources + + + {CEEEEB41-3059-46A2-A871-2ADE22C013D9} + ln.http + + + + + + + \ No newline at end of file diff --git a/service/ApplicationServiceBase.cs b/service/ApplicationServiceBase.cs new file mode 100644 index 0000000..801bf5c --- /dev/null +++ b/service/ApplicationServiceBase.cs @@ -0,0 +1,108 @@ +using System; +using System.Threading; +using ln.logging; +using System.Collections.Generic; +using System.Linq; + +namespace ln.application.service +{ + public abstract class ApplicationServiceBase : IDisposable + { + public int TimeOut { get; set; } = 10000; + + public String ServiceName { get; protected set; } + public virtual bool IsAlive => ((ServiceThread != null) && ServiceThread.IsAlive); + + public bool StopRequested { get; protected set; } + public Thread ServiceThread { get; protected set; } + + public string ServiceStateText { get; protected set; } + + public IApplicationInterface CurrentApplicationInterface { get; protected set; } + + public Type[] DependingServiceTypes => dependingServiceTypes.ToArray(); + HashSet dependingServiceTypes = new HashSet(); + + public ApplicationServiceBase(String serviceName) + { + ServiceName = serviceName; + ServiceStateText = "Initialized"; + } + + public void Dispose() + { + ServiceStateText = "Disposed"; + } + + protected void DependOnService() => dependingServiceTypes.Add(typeof(S)); + + public T Dependency() where T : ApplicationServiceBase + { + T serviceBase = (T)CurrentApplicationInterface.ServiceContainer[typeof(T)].ServiceBase; + if (serviceBase == null) + throw new EntryPointNotFoundException(String.Format("depending service not found: {0}",typeof(T).ToString())); + + return serviceBase; + } + + public virtual void ServiceMain(IApplicationInterface applicationInterface) => throw new NotImplementedException(); + + public virtual bool Start(IApplicationInterface applicationInterface, String[] arguments) + { + if (IsAlive) + throw new NotSupportedException("Service is already alive"); + + CurrentApplicationInterface = applicationInterface; + + foreach (Type dep in dependingServiceTypes) + { + ServiceDefinition depService = CurrentApplicationInterface.ServiceContainer[dep]; + if (!depService.IsAlive) + { + CurrentApplicationInterface.ServiceContainer.Start(depService); + if (!depService.IsAlive) + { + Logging.Log(LogLevel.ERROR, "service {0} could not start depending service {1}", ServiceName, depService.ServiceClassName); + return false; + } + } + } + + + StopRequested = false; + ServiceThread = new Thread(() => ServiceMain(applicationInterface)); + ServiceThread.Start(); + + return true; + } + + public virtual bool Stop() => Stop(false); + public virtual bool Stop(bool force) + { + if (IsAlive) + { + StopRequested = true; + lock (ServiceThread) + { + Monitor.PulseAll(ServiceThread); + } + ServiceThread.Join(TimeOut); + if (IsAlive && force) + { + Logging.Log(LogLevel.INFO, "Service did not shutdown, enforcing {0}",ServiceName); + ServiceThread.Abort(); + ServiceThread.Join(TimeOut); + } + + if (!ServiceThread.IsAlive) + CurrentApplicationInterface = null; + + return !ServiceThread.IsAlive; + } + return true; + } + + + + } +} diff --git a/service/ServiceContainer.cs b/service/ServiceContainer.cs new file mode 100644 index 0000000..901a14f --- /dev/null +++ b/service/ServiceContainer.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections; +using System.Collections.Generic; +namespace ln.application.service +{ + public class ServiceContainer : IEnumerable + { + public Application Application { get; } + + HashSet serviceDefinitions = new HashSet(); + + public ServiceContainer(Application application) + { + Application = application; + } + + public void Add(ServiceDefinition serviceDefinition) + { + serviceDefinitions.Add(serviceDefinition); + } + public void Remove(ServiceDefinition serviceDefinition) + { + serviceDefinitions.Remove(serviceDefinition); + } + + public ServiceDefinition this[string serviceClassName] + { + get + { + foreach (ServiceDefinition serviceDefinition in serviceDefinitions) + { + if (serviceDefinition.ServiceClassName.Equals(serviceClassName)) + return serviceDefinition; + } + throw new KeyNotFoundException(); + } + } + public ServiceDefinition this[string assemblyName,string serviceClassName] + { + get + { + foreach (ServiceDefinition serviceDefinition in serviceDefinitions) + { + if (serviceDefinition.ServiceClassName.Equals(serviceClassName) && Object.Equals(serviceDefinition.AssemblyName,assemblyName)) + return serviceDefinition; + } + throw new KeyNotFoundException(); + } + } + public ServiceDefinition this[Type serviceType] + { + get + { + return this[serviceType.Assembly.FullName,serviceType.FullName]; + } + } + + public void Start(ServiceDefinition serviceDefinition) + { + if (!serviceDefinitions.Contains(serviceDefinition)) + throw new NotSupportedException("ServiceDefinition not known to ServiceContainer"); + + ServiceDefinition sd = this[serviceDefinition.AssemblyName, serviceDefinition.ServiceClassName]; + sd.Start(this.Application); + } + public void Stop(ServiceDefinition serviceDefinition) + { + if (!serviceDefinitions.Contains(serviceDefinition)) + throw new NotSupportedException("ServiceDefinition not known to ServiceContainer"); + + ServiceDefinition sd = this[serviceDefinition.AssemblyName, serviceDefinition.ServiceClassName]; + sd.Stop(); + } + public void Load(ServiceDefinition serviceDefinition) + { + if (!serviceDefinitions.Contains(serviceDefinition)) + throw new NotSupportedException("ServiceDefinition not known to ServiceContainer"); + + ServiceDefinition sd = this[serviceDefinition.AssemblyName, serviceDefinition.ServiceClassName]; + sd.Load(); + } + public void Unload(ServiceDefinition serviceDefinition) + { + if (!serviceDefinitions.Contains(serviceDefinition)) + throw new NotSupportedException("ServiceDefinition not known to ServiceContainer"); + + ServiceDefinition sd = this[serviceDefinition.AssemblyName, serviceDefinition.ServiceClassName]; + sd.Unload(); + } + + + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)serviceDefinitions).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)serviceDefinitions).GetEnumerator(); + } + } +} diff --git a/service/ServiceDefinition.cs b/service/ServiceDefinition.cs new file mode 100644 index 0000000..c8922eb --- /dev/null +++ b/service/ServiceDefinition.cs @@ -0,0 +1,119 @@ +using System; +using System.Reflection; +using ln.logging; +using System.IO; +using System.Linq; +namespace ln.application.service +{ + public class ServiceDefinition + { + public String AssemblyName { get; } + public String ServiceClassName { get; } + public bool AutoStart { get; set; } + + public ApplicationServiceBase ServiceBase { get; private set; } + + public bool IsLoaded => ServiceBase != null; + public bool IsAlive => IsLoaded && ServiceBase.IsAlive; + + Assembly serviceAssembly; + Type serviceType; + + public ServiceDefinition(String assemblyName, string serviceClassName) + { + AssemblyName = assemblyName; + ServiceClassName = serviceClassName; + } + public ServiceDefinition(string serviceClassName) + { + AssemblyName = null; + ServiceClassName = serviceClassName; + } + public static ServiceDefinition From() => From(true); + public static ServiceDefinition From(bool autoStart) + { + Type st = typeof(S); + ServiceDefinition sd = new ServiceDefinition( + st.Assembly.FullName, + st.FullName + ); + sd.AutoStart = autoStart; + return sd; + } + + public void Load() + { + if (IsLoaded) + throw new NotSupportedException(String.Format("Service {0} [{1}] is already loaded",ServiceClassName,AssemblyName)); + + if (AssemblyName != null) + serviceAssembly = Assembly.Load(AssemblyName); + + serviceType = FinalAssembly.GetType(ServiceClassName); + if (serviceType == null) + throw new EntryPointNotFoundException(String.Format("could not get type {0}",ServiceClassName)); + + if (!serviceType.IsSubclassOf(typeof(ApplicationServiceBase))) + throw new NotSupportedException(String.Format("application service type must derive from ApplicationServiceBase")); + + ServiceBase = (ApplicationServiceBase)Activator.CreateInstance(serviceType); + } + public void Unload() => Unload(false); + public void Unload(bool force) + { + if (!IsLoaded) + throw new NotSupportedException(String.Format("Service {0} [{1}] is not loaded", ServiceClassName, AssemblyName)); + + if (!force && IsAlive) + throw new NotSupportedException(String.Format("Service {0} [{1}] still alive", ServiceClassName, AssemblyName)); + + ServiceBase.Dispose(); + + ServiceBase = null; + serviceType = null; + serviceAssembly = null; + } + public void Start(IApplicationInterface applicationInterface) + { + if (!IsLoaded) + Load(); + + Logging.Log(LogLevel.INFO, "service: starting {0} [{1}]", ServiceClassName, FinalAssembly.GetName().Name); + + if (ServiceBase.Start(applicationInterface,new string[0])) + Logging.Log(LogLevel.INFO, "service: started {0} [{1}]", ServiceClassName, FinalAssembly.GetName().Name); + else + Logging.Log(LogLevel.ERROR, "service: failed {0} [{1}]", ServiceClassName, FinalAssembly.GetName().Name); + } + public void Stop() + { + if (IsAlive) + { + Logging.Log(LogLevel.INFO, "service: stopping {0} [{1}]", ServiceClassName, FinalAssembly.GetName().Name); + + if (ServiceBase.Stop()) + Logging.Log(LogLevel.INFO, "service: stopped {0} [{1}]", ServiceClassName, FinalAssembly.GetName().Name); + else + Logging.Log(LogLevel.ERROR, "service: stopfail {0} [{1}]", ServiceClassName, FinalAssembly.GetName().Name); + } + } + + private Assembly FinalAssembly => serviceAssembly != null ? serviceAssembly : Assembly.GetEntryAssembly(); + + public override int GetHashCode() + { + return (AssemblyName != null ? AssemblyName.GetHashCode() : 0) ^ ServiceClassName.GetHashCode(); + } + public override bool Equals(object obj) + { + if (obj is ServiceDefinition) + { + ServiceDefinition other = obj as ServiceDefinition; + return Object.Equals(AssemblyName, other.AssemblyName) && ServiceClassName.Equals(other.ServiceClassName); + } + return false; + } + + + } +}