746 lines
22 KiB
C#
746 lines
22 KiB
C#
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
|
|
}
|
|
}
|