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
{
///
/// A kiszolgáló.
///
///
/// Az osztály nem engedi meg, hogy egyszerre több példány is létezzen egyidejűleg.
///
public abstract class SDAServer : IDisposable
{
#region Mezők
///
/// Szálbiztos védelem az egyedüli példány létrehozásához.
///
protected static readonly object SyncClass = new object();
///
/// Szálbiztos védelem.
///
protected readonly object Sync = new object();
///
/// A kiszolgáló konfigurációs XML címkéje.
///
protected XmlNode ConfigurationNode;
///
/// A leállást okozó kivétel.
///
Exception _stopException;
///
/// Többszörös felszabadítás elleni védelem.
///
bool _disposed;
#endregion
#region Konstruktorok
///
/// Az osztály konstruktora.
///
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;
}
}
///
/// Az osztály konstruktora.
///
/// A konfigurációt tartalmazó XmlNode
protected SDAServer(XmlNode configNode)
: this()
{
try
{
ConfigurationNode = configNode ?? throw new ArgumentNullException(nameof(configNode));
}
catch
{
Dispose();
throw;
}
}
#endregion
#region Tulajdonságok
///
/// A kiszolgáló egyetlen példánya, ha van.
///
public static SDAServer Instance { get; protected set; }
///
/// A kiszolgáló futási állapota.
///
public bool IsRunning { get; private set; }
///
/// A kiszolgáló utolsó indítási ideje.
///
public DateTime LastStartup { get; private set; }
///
/// A kiszolgáló konfigurációja.
///
public Configuration Configuration { get; private set; }
///
/// A kiszolgáló naplózó funkciókat tartalmazó objektuma.
///
public Logger Logger { get; protected set; }
///
/// A kiszolgáló munkamenet-kezelő objektuma.
///
public SessionManager SessionManager { get; private set; }
///
/// A kiszolgáló gyorsítótárait kezelő segédobjektuma.
///
public Kreta.Framework.Caching.CacheManager CacheManager { get; private set; }
///
/// Session kapcsolatot kezelő szolgáltatás.
///
public ConnectionManager ConnectionManager { get; protected set; }
#endregion
#region Absztrakt tulajdonságok
public abstract bool ConsoleEnabled { get; set; }
#endregion
#region Nyilvános felület
///
/// Eldönti hogy egy assembly feldolgozása engedélyezett-e a szerver reflexiós metódusainak.
///
/// Az adott assembly.
/// ha igen, egyébként.
internal protected virtual bool IsAssemblyAllowed(Assembly assembly)
{
return true;
}
///
/// Adatbázis kapcsolathoz egy példány létrehozása, zárt állapotban.
///
public SDAConnection CreateConnection()
{
// <>
return new SDAConnection(Configuration.DBConnection);
}
public SDAConnection CreateConnection(string connectionString)
{
// <>
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;
}
///
/// Karakterlánccá alakítja az objektumot.
///
///
/// A karakterlánc tartalmazza a kiszolgáló nevét, hálózati portszámát, állapotát (fut/nem fut).
///
/// Az objektumot leíró karakterlánc.
public override string ToString()
{
if (Configuration == null)
{
return "SDAServer";
}
return string.Format(
IsRunning ? "{0} (running)" : "{0} (stopped)",
Configuration.ServerName);
}
///
/// A konfiguráció adott nevű tulajdonsága.
///
/// A tulajdonság neve.
/// A tulajdonság aktuális értéke.
public virtual object GetConfigurationParameter(string parametername)
{
return Configuration.GetType().InvokeMember(parametername, BindingFlags.GetProperty, null, Configuration, null);
}
#region Indítás, leállítás
///
/// Elindítja a kiszolgálót.
///
public void Start()
{
// <>
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);
}
}
}
///
/// Leállítja a kiszolgálót.
///
public void Stop()
{
lock (Sync)
{
if (!IsRunning)
{
return;
}
IsRunning = false;
DoStop();
}
}
///
/// Újraindítja a kiszolgálót.
///
///
/// A végrehajtás egyenértékű egy leállítással és egy indítással.
///
public void Restart()
{
// <>
lock (Sync)
{
if (!IsRunning)
{
return;
}
Stop();
Start();
}
}
///
/// Biztonságosan leállítja a kiszolgálót, és hibaüzenetet hagy maga után, a leállás pontos okáról.
///
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
///
/// Elvégzi a kiszolgáló indítását.
///
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);
}
}
///
/// Elvégzi a kiszolgáló leállítását.
///
protected virtual void DoStop()
{
lock (Sync)
{
if (SessionManager != null)
{
SessionManager.Dispose();
SessionManager = null;
}
if (Logger != null)
{
Logger.ServerStopped();
Logger = null;
}
CacheManager = null;
}
}
///
/// Konfiguráció felolvasása.
///
/// A beállításokat tartalmazó Xml leíró.
protected virtual Configuration DoReadConfiguration(XmlNode configNode)
{
// <>
return new Configuration(configNode);
}
///
/// Létrehozza az alapértelmezett naplóvezető szolgáltatást.
///
protected virtual ILogWriter CreatePrimaryLogWriter()
{
// <>
return LogUtil.ConfigureLogWriters(Configuration);
}
///
/// Létrehozza a kiszolgáló munkamenet-kezelőjét.
///
protected virtual SessionManager CreateSessionManager(Configuration configuration, Caching.CacheManager cacheManager)
{
// <>
return new SessionManager(configuration, cacheManager);
}
///
/// Létrehozza a kiszolgáló gyorsítótár-kezelőjét.
///
protected virtual Kreta.Framework.Caching.CacheManager CreateCacheManager()
{
// <>
return new Kreta.Framework.Caching.CacheManager();
}
///
/// Inicializálja a kiszolgáló gyorsítótár-kezelőjét, és létrehozza az alap gyorsítótárakat.
///
protected virtual void InitCacheManager()
{
CacheManager.AquireCache();
CacheManager.AquireCache();
}
///
/// Inicializálja az adatbázis-kapcsolatot.
///
///
/// A metódus létrehozza és megnyitja az adatbázis-kapcsolatot és ellenőrzi, hogy
/// megfelelő-e az adatbázis verziója.
///
/// Konfigurációs objektum.
protected virtual void DoInitDatabase(Configuration configuration)
{
Initializer.Initialize(DatabaseType.NativeMSSQL);
SDA.DataProvider.Configuration.ErrorRecovery = true;
SDA.DataProvider.Configuration.MaxTries = 10;
}
#endregion
#region Pánik
///
/// Ismeretlen eredetű kivételt kezel le.
///
/// A kivétel, ami történt.
/// , ha a kiszolgáló újraindítható; egyébként .
protected virtual void DoPanic(Exception exception)
{
string panicmessage = BuildPanicMessage(exception);
LogLastWords(panicmessage);
WriteLastWords(panicmessage);
}
///
/// A leállás naplózása a rendszernaplóba.
///
/// A naplóbejegyzés szövege.
[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) { }
}
///
/// Elkészíti a pánik üzenetet.
///
/// A pánikot okozó kivétel.
/// A szöveges üzenet.
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();
}
///
/// Kiírja a pánik üzenetet konzolra és fájlba.
///
/// A hátrahagyandó üzenet
/// , ha sikerült; egyébként
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
///
/// A kiszolgáló konfigurációs XML fájljának elérési útvonala.
///
readonly string _configurationPath;
#endregion
#region Tulajdonságok
public override bool ConsoleEnabled
{
get { return ConsoleLogWriter.ConsoleEnabled; }
set { ConsoleLogWriter.ConsoleEnabled = value; }
}
#endregion
#region Konstruktorok
///
/// Az osztály konstruktora.
///
SDAApplicationServer()
{
try
{
Entities.EntityHandler.Init();
}
catch
{
Dispose();
throw;
}
}
///
/// Az osztály konstruktora.
///
/// A konfigurációs XML állomány elérési útvonala
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
}
}