This commit is contained in:
skidoodle 2024-03-13 00:33:46 +01:00
commit e124a47765
19374 changed files with 9806149 additions and 0 deletions

746
Framework/Util/SDAServer.cs Normal file
View file

@ -0,0 +1,746 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Permissions;
using System.Text;
using System.Xml;
using Kreta.Framework.Caching;
using Kreta.Framework.Exceptions;
using Kreta.Framework.Logging;
using Kreta.Framework.Session;
using SDA.DataProvider;
namespace Kreta.Framework
{
/// <summary>
/// A kiszolgáló.
/// </summary>
/// <remarks>
/// Az osztály nem engedi meg, hogy egyszerre több példány is létezzen egyidejűleg.
/// </remarks>
public abstract class SDAServer : IDisposable
{
#region Mezők
/// <summary>
/// Szálbiztos védelem az egyedüli példány létrehozásához.
/// </summary>
protected static readonly object SyncClass = new object();
/// <summary>
/// Szálbiztos védelem.
/// </summary>
protected readonly object Sync = new object();
/// <summary>
/// A kiszolgáló konfigurációs XML címkéje.
/// </summary>
protected XmlNode ConfigurationNode;
/// <summary>
/// A leállást okozó kivétel.
/// </summary>
Exception _stopException;
/// <summary>
/// Többszörös felszabadítás elleni védelem.
/// </summary>
bool _disposed;
#endregion
#region Konstruktorok
/// <summary>
/// Az osztály konstruktora.
/// </summary>
protected SDAServer()
{
const string errorMessage = "There is a running server.";
if (Instance != null)
{
throw new InvalidOperationException(errorMessage);
}
lock (SyncClass)
{
if (Instance != null)
{
throw new InvalidOperationException(errorMessage);
}
Instance = this;
}
}
/// <summary>
/// Az osztály konstruktora.
/// </summary>
/// <param name="configNode">A konfigurációt tartalmazó XmlNode</param>
protected SDAServer(XmlNode configNode)
: this()
{
try
{
ConfigurationNode = configNode ?? throw new ArgumentNullException(nameof(configNode));
}
catch
{
Dispose();
throw;
}
}
#endregion
#region Tulajdonságok
/// <summary>
/// A kiszolgáló egyetlen példánya, ha van.
/// </summary>
public static SDAServer Instance { get; protected set; }
/// <summary>
/// A kiszolgáló futási állapota.
/// </summary>
public bool IsRunning { get; private set; }
/// <summary>
/// A kiszolgáló utolsó indítási ideje.
/// </summary>
public DateTime LastStartup { get; private set; }
/// <summary>
/// A kiszolgáló konfigurációja.
/// </summary>
public Configuration Configuration { get; private set; }
/// <summary>
/// A kiszolgáló naplózó funkciókat tartalmazó objektuma.
/// </summary>
public Logger Logger { get; protected set; }
/// <summary>
/// A kiszolgáló munkamenet-kezelő objektuma.
/// </summary>
public SessionManager SessionManager { get; private set; }
/// <summary>
/// A kiszolgáló gyorsítótárait kezelő segédobjektuma.
/// </summary>
public Kreta.Framework.Caching.CacheManager CacheManager { get; private set; }
/// <summary>
/// Session kapcsolatot kezelő szolgáltatás.
/// </summary>
public ConnectionManager ConnectionManager { get; protected set; }
#endregion
#region Absztrakt tulajdonságok
public abstract bool ConsoleEnabled { get; set; }
#endregion
#region Nyilvános felület
/// <summary>
/// Eldönti hogy egy assembly feldolgozása engedélyezett-e a szerver reflexiós metódusainak.
/// </summary>
/// <param name="assembly">Az adott assembly.</param>
/// <returns><see langword="true"/> ha igen, <see langword="false"/> egyébként.</returns>
internal protected virtual bool IsAssemblyAllowed(Assembly assembly)
{
return true;
}
/// <summary>
/// Adatbázis kapcsolathoz egy példány létrehozása, zárt állapotban.
/// </summary>
public SDAConnection CreateConnection()
{
// <<Factory Method>>
return new SDAConnection(Configuration.DBConnection);
}
public SDAConnection CreateConnection(string connectionString)
{
// <<Factory Method>>
return new SDAConnection(connectionString);
}
public virtual string GetOrganizationIdentifier()
{
return string.Empty;
}
public virtual string GetIntezmenyConnectionString(string intezmenyAzonosito)
{
return Configuration.DBConnection;
}
public virtual string GetSystemConnectionString(string intezmenyAzonosito)
{
return Configuration.DBConnection;
}
/// <summary>
/// Karakterlánccá alakítja az objektumot.
/// </summary>
/// <remarks>
/// A karakterlánc tartalmazza a kiszolgáló nevét, hálózati portszámát, állapotát (fut/nem fut).
/// </remarks>
/// <returns>Az objektumot leíró karakterlánc.</returns>
public override string ToString()
{
if (Configuration == null)
{
return "SDAServer";
}
return string.Format(
IsRunning ? "{0} (running)" : "{0} (stopped)",
Configuration.ServerName);
}
/// <summary>
/// A konfiguráció adott nevű tulajdonsága.
/// </summary>
/// <param name="parametername">A tulajdonság neve.</param>
/// <returns>A tulajdonság aktuális értéke.</returns>
public virtual object GetConfigurationParameter(string parametername)
{
return Configuration.GetType().InvokeMember(parametername, BindingFlags.GetProperty, null, Configuration, null);
}
#region Indítás, leállítás
/// <summary>
/// Elindítja a kiszolgálót.
/// </summary>
public void Start()
{
// <<Template Method>>
lock (Sync)
{
if (IsRunning)
{
return;
}
try
{
IsRunning = true;
DoStart();
Logger.ServerStarted();
LastStartup = DateTime.Now;
}
catch (ServerStartException)
{
IsRunning = false;
throw;
}
catch (PanicException)
{
IsRunning = false;
}
catch (Exception exception)
{
if (Logger != null)
{
try
{
Logger.ExceptionThrown(exception);
}
catch (Exception logException)
{
exception = new AggregateException(exception, logException);
}
}
IsRunning = false;
throw new ServerStartException(exception);
}
}
}
/// <summary>
/// Leállítja a kiszolgálót.
/// </summary>
public void Stop()
{
lock (Sync)
{
if (!IsRunning)
{
return;
}
IsRunning = false;
DoStop();
}
}
/// <summary>
/// Újraindítja a kiszolgálót.
/// </summary>
/// <remarks>
/// A végrehajtás egyenértékű egy leállítással és egy indítással.
/// </remarks>
public void Restart()
{
// <<Template Method>>
lock (Sync)
{
if (!IsRunning)
{
return;
}
Stop();
Start();
}
}
/// <summary>
/// Biztonságosan leállítja a kiszolgálót, és hibaüzenetet hagy maga után, a leállás pontos okáról.
/// </summary>
public void Panic(Exception panicException)
{
lock (Sync)
{
if (_stopException != null)
{
throw panicException;
}
_stopException = panicException;
try
{
DoPanic(panicException);
}
finally
{
try
{
Stop();
}
catch (Exception stopexception)
{
// még leállítani sem lehet...
ConsoleLogWriter.WriteLine("A kiszolgáló leállítása nem sikerült.");
ConsoleLogWriter.WriteLine(ExceptionUtil.ExceptionToString(stopexception));
}
}
}
throw new PanicException(panicException);
}
#endregion
#endregion
#region Indítás, leállítás
/// <summary>
/// Elvégzi a kiszolgáló indítását.
/// </summary>
protected virtual void DoStart()
{
lock (Sync)
{
_stopException = null;
Configuration = DoReadConfiguration(ConfigurationNode);
Logger = new Logger(CreatePrimaryLogWriter());
Logger.ConfigurationLoaded();
DoInitDatabase(Configuration);
CacheManager = CreateCacheManager();
InitCacheManager();
LanguageContext.Initialize(new LanguageContext(Configuration.LCID));
SessionManager = CreateSessionManager(Configuration, CacheManager);
}
}
/// <summary>
/// Elvégzi a kiszolgáló leállítását.
/// </summary>
protected virtual void DoStop()
{
lock (Sync)
{
if (SessionManager != null)
{
SessionManager.Dispose();
SessionManager = null;
}
if (Logger != null)
{
Logger.ServerStopped();
Logger = null;
}
CacheManager = null;
}
}
/// <summary>
/// Konfiguráció felolvasása.
/// </summary>
/// <param name="configNode">A beállításokat tartalmazó Xml leíró.</param>
protected virtual Configuration DoReadConfiguration(XmlNode configNode)
{
// <<Factory Method>>
return new Configuration(configNode);
}
/// <summary>
/// Létrehozza az alapértelmezett naplóvezető szolgáltatást.
/// </summary>
protected virtual ILogWriter CreatePrimaryLogWriter()
{
// <<Factory Method>>
return LogUtil.ConfigureLogWriters(Configuration);
}
/// <summary>
/// Létrehozza a kiszolgáló munkamenet-kezelőjét.
/// </summary>
protected virtual SessionManager CreateSessionManager(Configuration configuration, Caching.CacheManager cacheManager)
{
// <<Factory Method>>
return new SessionManager(configuration, cacheManager);
}
/// <summary>
/// Létrehozza a kiszolgáló gyorsítótár-kezelőjét.
/// </summary>
protected virtual Kreta.Framework.Caching.CacheManager CreateCacheManager()
{
// <<Factory Method>>
return new Kreta.Framework.Caching.CacheManager();
}
/// <summary>
/// Inicializálja a kiszolgáló gyorsítótár-kezelőjét, és létrehozza az alap gyorsítótárakat.
/// </summary>
protected virtual void InitCacheManager()
{
CacheManager.AquireCache<StringResourcesCache>();
CacheManager.AquireCache<ManualEnumCache>();
}
/// <summary>
/// Inicializálja az adatbázis-kapcsolatot.
/// </summary>
/// <remarks>
/// A metódus létrehozza és megnyitja az adatbázis-kapcsolatot és ellenőrzi, hogy
/// megfelelő-e az adatbázis verziója.
/// </remarks>
/// <param name="configuration">Konfigurációs objektum.</param>
protected virtual void DoInitDatabase(Configuration configuration)
{
Initializer.Initialize(DatabaseType.NativeMSSQL);
SDA.DataProvider.Configuration.ErrorRecovery = true;
SDA.DataProvider.Configuration.MaxTries = 10;
}
#endregion
#region Pánik
/// <summary>
/// Ismeretlen eredetű kivételt kezel le.
/// </summary>
/// <param name="exception">A kivétel, ami történt.</param>
/// <returns><see langword="true"/>, ha a kiszolgáló újraindítható; egyébként <see langword="false"/>.</returns>
protected virtual void DoPanic(Exception exception)
{
string panicmessage = BuildPanicMessage(exception);
LogLastWords(panicmessage);
WriteLastWords(panicmessage);
}
/// <summary>
/// A leállás naplózása a rendszernaplóba.
/// </summary>
/// <param name="panicmessage">A naplóbejegyzés szövege.</param>
[EventLogPermission(SecurityAction.Demand, PermissionAccess = EventLogPermissionAccess.Write)]
protected virtual void LogLastWords(string panicmessage)
{
try
{
if (!EventLog.SourceExists(Configuration.ServerName))
{
EventLog.CreateEventSource(Configuration.ServerName, "SDA");
}
var log = new EventLog
{
Log = "SDA",
Source = Configuration.ServerName
};
log.WriteEntry(panicmessage, EventLogEntryType.Error, (int)Events.CRITICAL_EXCEPTION);
}
catch (System.Security.SecurityException) { }
catch (InvalidOperationException) { }
catch (Win32Exception) { }
}
/// <summary>
/// Elkészíti a pánik üzenetet.
/// </summary>
/// <param name="exception">A pánikot okozó kivétel.</param>
/// <returns>A szöveges üzenet.</returns>
protected virtual string BuildPanicMessage(Exception exception)
{
var builder = new StringBuilder();
builder.AppendLine("Server panic:");
builder.AppendLine(DateTime.Now.ToString("yyyy.MM.dd. HH:mm:ss"));
if (exception != null)
{
builder.AppendLine("Exception:");
builder.Append(exception).AppendLine();
}
builder.AppendLine("Call stack:");
builder.Append(new StackTrace()).AppendLine();
return builder.ToString();
}
/// <summary>
/// Kiírja a pánik üzenetet konzolra és fájlba.
/// </summary>
/// <param name="panicMessage">A hátrahagyandó üzenet</param>
/// <returns><see langword="true"/>, ha sikerült; egyébként <see langword="false"/></returns>
protected virtual void WriteLastWords(string panicMessage)
{
try
{
ConsoleLogWriter.WriteLine(panicMessage, LogLevel.FATAL);
if (!ConsoleLogWriter.ConsoleEnabled)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine(panicMessage);
Console.ResetColor();
}
}
catch (Exception exception)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("SDAServer.WriteLastWords():");
Console.WriteLine(exception);
Console.ResetColor();
}
try
{
string directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "LastWords");
string fileName = string.Format(CultureInfo.InvariantCulture, @"LW{0:yyyyMMddHHmmss}.txt", DateTime.Now);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (var writer = new StreamWriter(Path.Combine(directory, fileName), true))
{
writer.WriteLine(panicMessage);
writer.WriteLine();
}
}
catch (Exception exception)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("SDAServer.WriteLastWords():");
Console.WriteLine(exception);
Console.ResetColor();
}
}
#endregion
#region IDisposable
protected virtual void Dispose(bool disposing)
{
try
{
if (_disposed)
{
return;
}
if (disposing)
{
DoStop();
}
_disposed = true;
}
finally
{
lock (SyncClass)
{
Instance = null;
}
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~SDAServer()
{
Dispose(false);
}
#endregion
}
public abstract class SDAApplicationServer : SDAServer
{
#region Mezők
/// <summary>
/// A kiszolgáló konfigurációs XML fájljának elérési útvonala.
/// </summary>
readonly string _configurationPath;
#endregion
#region Tulajdonságok
public override bool ConsoleEnabled
{
get { return ConsoleLogWriter.ConsoleEnabled; }
set { ConsoleLogWriter.ConsoleEnabled = value; }
}
#endregion
#region Konstruktorok
/// <summary>
/// Az osztály konstruktora.
/// </summary>
SDAApplicationServer()
{
try
{
Entities.EntityHandler.Init();
}
catch
{
Dispose();
throw;
}
}
/// <summary>
/// Az osztály konstruktora.
/// </summary>
/// <param name="configPath">A konfigurációs XML állomány elérési útvonala</param>
protected SDAApplicationServer(string configPath)
: this()
{
try
{
if (string.IsNullOrWhiteSpace(configPath))
{
throw new ArgumentNullException(nameof(configPath));
}
_configurationPath = configPath;
}
catch
{
Dispose();
throw;
}
}
protected SDAApplicationServer(XmlNode configNode)
: this()
{
try
{
ConfigurationNode = configNode ?? throw new ArgumentNullException(nameof(configNode));
}
catch
{
Dispose();
throw;
}
}
#endregion
protected virtual XmlNode LoadConfig(string path)
{
var doc = new XmlDocument();
using (var reader = new StreamReader(path))
{
doc.LoadXml(reader.ReadToEnd());
}
return doc.SelectSingleNode(".//config");
}
protected override void DoStart()
{
lock (Sync)
{
Logger = new Logger(new ConsoleLogWriter());
if (_configurationPath != null)
{
ConfigurationNode = LoadConfig(_configurationPath);
}
base.DoStart();
}
}
void DoStopImpl()
{
}
protected override void DoStop()
{
lock (Sync)
{
try
{
DoStopImpl();
}
finally
{
base.DoStop();
}
}
}
internal protected override bool IsAssemblyAllowed(Assembly assembly)
{
return
assembly.FullName.IndexOf(@"interop", StringComparison.OrdinalIgnoreCase) == -1
&&
assembly.FullName.IndexOf(@"fastreport", StringComparison.OrdinalIgnoreCase) == -1
&&
assembly.FullName.IndexOf(@"barcode", StringComparison.OrdinalIgnoreCase) == -1
&&
assembly.FullName.IndexOf(@"manageddataaccessdtc", StringComparison.OrdinalIgnoreCase) == -1;
}
#region IDisposable
bool _disposed;
protected override void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
DoStopImpl();
}
base.Dispose(disposing);
_disposed = true;
}
#endregion
}
}