using bsmd.database; using bsmd.status; using log4net; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.ServiceProcess; using System.Timers; namespace SendNSWMessageService { public partial class NSWSendService : ServiceBase { private Timer _timer; private object _timerlock = new object(); private bool processRunning = false; private ILog _log = LogManager.GetLogger(typeof(NSWSendService)); public NSWSendService() { Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); InitializeComponent(); System.Net.ServicePointManager.ServerCertificateValidationCallback += delegate (object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors) { return true; // **** Immer OK weil wir nur mit einem dedizierten Endpoint reden.. }; } public void Commandline(string[] args) { // Dieser Modus dient allein dazu, eine separate Instanz "nebenher" laufen zu lassen, ohne // sie als Service zu installieren. Dies ist sinnvoll um für spezielle HIS Einstellungen Tests zu machen // ohne den laufenden Betrieb zu stören (hoffentlich!) this.Init(args); // setup timer this.DoOnce(); while (true) System.Threading.Thread.Sleep(1000); // sleep until someone kills me :) } protected override void OnStart(string[] args) { this.EventLog.Source = this.ServiceName; this.EventLog.Log = "Application"; this.Init(args); System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); string version = fvi.FileVersion; this.EventLog.WriteEntry("NSW Send Service started", EventLogEntryType.Information); _log.InfoFormat("NSW Send Service started. v.{0}", version); this.DoOnce(); } public void Init(string[] args) { this._timer = new Timer(); this._timer.Interval = Properties.Settings.Default.SleepSeconds * 1000; this._timer.Elapsed += _timer_Elapsed; this._timer.Enabled = true; } public void DoOnce() { this._timer_Elapsed(null, null); } void _timer_Elapsed(object sender, ElapsedEventArgs e) { lock (this._timerlock) { if (this.processRunning) return; else this.processRunning = true; } if (DBManager.Instance.Connect(Properties.Settings.Default.ConnectionString)) { // Datenbank auf zu sendende Objekte überprüfen und laden List coresMarkedForSending = DBManager.Instance.GetMessageCoresByStatus(MessageCore.BSMDStatus.TOSEND); foreach (MessageCore core in coresMarkedForSending) { List messages = DBManager.Instance.GetMessagesForCore(core, DBManager.MessageLoad.ALL); List toSendMessageList = new List(); bool coreSendSucceeded = true; bool didSendSomething = false; // Wenn es noch keine Visit-ID gibt können keine Meldeklassen versendet werden. Die Visit/Transit Meldung wird daher dann // "leer" versendet if ((core.IsTransit && core.TransitId.IsNullOrEmpty()) || (!core.IsTransit && core.VisitId.IsNullOrEmpty())) { foreach (Message message in messages) { if (((message.MessageNotificationClass == Message.NotificationClass.VISIT) && (!core.IsTransit)) || ((message.MessageNotificationClass == Message.NotificationClass.TRANSIT) && (core.IsTransit))) { if ((message.InternalStatus == Message.BSMDStatus.UNDEFINED) || (message.InternalStatus == Message.BSMDStatus.PREPARE) || (message.InternalStatus == Message.BSMDStatus.TOSEND)) { _log.Debug("Visit/Transit not found, sending VISIT/TRANSIT message"); if (message.HIS == Message.NSWProvider.UNDEFINED) message.HIS = core.InitialHIS; if (core.DefaultReportingPartyId.HasValue) { message.ReportingPartyId = core.DefaultReportingPartyId; // Referenz umbiegen if (DBManager.Instance.GetReportingPartyDict().ContainsKey(core.DefaultReportingPartyId.Value)) // geladenes Objekt ersetzen message.ReportingParty = DBManager.Instance.GetReportingPartyDict()[core.DefaultReportingPartyId.Value]; } toSendMessageList.Add(message); } } } } else { // VISIT / TRANSIT Id vorhanden, Daten können übertragen werden foreach (Message message in messages) { // Update 5.10.17: Alles was nicht explizit auf "TOSEND" steht wird nicht verschickt! if (message.InternalStatus != Message.BSMDStatus.TOSEND) continue; // "Virtuelle" Meldeklassen (bisher: DK) werden hier nicht versendet! if ((message.MessageNotificationClass == Message.NotificationClass.STO) || (message.MessageNotificationClass == Message.NotificationClass.CREWD) || (message.MessageNotificationClass == Message.NotificationClass.PASD)) continue; if ((message.MessageNotificationClass == Message.NotificationClass.VISIT) || (message.MessageNotificationClass == Message.NotificationClass.TRANSIT)) continue; if (!XtraSendLogic.ShouldSendMessage(message)) { message.InternalStatus = Message.BSMDStatus.SUSPENDED; DBManager.Instance.Save(message); continue; } if (core.DefaultReportingPartyId.HasValue) { message.ReportingPartyId = core.DefaultReportingPartyId; // Referenz umbiegen if (DBManager.Instance.GetReportingPartyDict().ContainsKey(core.DefaultReportingPartyId.Value)) // geladenes Objekt ersetzen message.ReportingParty = DBManager.Instance.GetReportingPartyDict()[core.DefaultReportingPartyId.Value]; } toSendMessageList.Add(message); } } foreach (Message message in toSendMessageList) { try { bool sendSucceeded = false; message.HIS = core.InitialHIS; _log.InfoFormat("Sending {0} message to {1}", message.MessageNotificationClass.ToString(), message.HIS.ToString()); // switch über passendes HIS / Schnittstelle switch (message.HIS) { case Message.NSWProvider.DBH: case Message.NSWProvider.DBH_TEST: sendSucceeded = bsmd.dbh.Request.SendMessage(message, (message.HIS == Message.NSWProvider.DBH_TEST)); if (!sendSucceeded) message.InternalStatus = Message.BSMDStatus.SEND_FAILED; else didSendSomething = true; break; case Message.NSWProvider.DAKOSY: case Message.NSWProvider.DAKOSY_TEST: sendSucceeded = bsmd.dakosy.Request.Send(message, true); if (!sendSucceeded) message.InternalStatus = Message.BSMDStatus.SEND_FAILED; else didSendSomething = true; break; case Message.NSWProvider.DUDR: case Message.NSWProvider.DUDR_TEST: bool? sendResult = bsmd.hisnord.Request.CreateSendFile(core, message, (core.InitialHIS == Message.NSWProvider.DUDR_TEST)); if (sendResult.HasValue) { didSendSomething = true; sendSucceeded = sendResult.Value; } break; default: _log.WarnFormat("Initial HIS not specified for message {0}", message.Id); break; } coreSendSucceeded &= sendSucceeded; if(sendSucceeded) { message.ChangedBy = ""; // Leeren nach RS mit CH: Sie möchte das Feld als Indikator "zu versenden" verwenden (ich war dagegen ;-) message.InternalStatus = Message.BSMDStatus.SENT; } else { message.InternalStatus = Message.BSMDStatus.SEND_FAILED; } message.StatusInfo = ""; message.SentAt = DateTime.Now; DBManager.Instance.DeleteSystemErrors(message); DBManager.Instance.Save(message); } catch (Exception ex) { _log.ErrorFormat("SENDING message {0}: {1}", message.Id.ToString(), ex.Message); } } if(!didSendSomething) // bisher nichts passiert, aber auf "TO_SEND", ist das ein Storno? { if (core.Cancelled ?? false) { switch(core.InitialHIS) { case Message.NSWProvider.DUDR: coreSendSucceeded = bsmd.hisnord.Request.CreateSendFile(core, null, false) ?? false; didSendSomething = true; break; case Message.NSWProvider.DUDR_TEST: coreSendSucceeded = bsmd.hisnord.Request.CreateSendFile(core, null, true) ?? false; didSendSomething = true; break; case Message.NSWProvider.DBH: break; case Message.NSWProvider.DBH_TEST: break; default: _log.WarnFormat("Cancelling for HIS {0} is not supported", core.InitialHIS); break; } } } if (didSendSomething) { // falls nur eine Nachricht gescheitert ist geht der Core auf SEND_FAILED, sonst FAILURE core.BSMDStatusInternal = coreSendSucceeded ? MessageCore.BSMDStatus.SENT : MessageCore.BSMDStatus.FAILURE; } else { core.BSMDStatusInternal = MessageCore.BSMDStatus.PREPARE; // aus irgendwelchen Gründen wurde nichts verschickt -> zurück auf PREPARE } DBManager.Instance.Save(core); /* if ((core.InitialHIS == Message.NSWProvider.DUDR) || (core.InitialHIS == Message.NSWProvider.DUDR_TEST)) { bool sendSucceeded = true; bool didSendSomething = false; // Fall: keine Meldeklasse aber trotzdem auf TO_SEND. Entweder beim Beantragen von Visit-Id's oder beim Storno der gesamten // Anmeldung if ((core.DisplayId.Length == 0) || (core.Cancelled ?? false)) { sendSucceeded = bsmd.hisnord.Request.CreateSendFile(core, messages, (core.InitialHIS == Message.NSWProvider.DUDR_TEST)) ?? false; } else { foreach (Message message in messages) { bool? sendResult = bsmd.hisnord.Request.CreateSendFile(core, message, (core.InitialHIS == Message.NSWProvider.DUDR_TEST)); if (sendResult.HasValue) { didSendSomething = true; if (!sendResult.Value) sendSucceeded = false; } } } if (!didSendSomething) core.BSMDStatusInternal = MessageCore.BSMDStatus.PREPARE; // zurück zu neutral else { if (!sendSucceeded) core.BSMDStatusInternal = MessageCore.BSMDStatus.FAILURE; else core.BSMDStatusInternal = MessageCore.BSMDStatus.SENT; } _log.InfoFormat("HIS-Nord Send: Core {0} [{1}] new Status {2}", core.Id, core.IMO, core.BSMDStatusInternal.ToString()); DBManager.Instance.Save(core); } else { bool sendSucceeded; #region DBH / Dakosy Logik List toSendMessageList = new List(); if ((core.IsTransit && core.TransitId.IsNullOrEmpty()) || (!core.IsTransit && core.VisitId.IsNullOrEmpty())) { foreach (Message message in messages) { if ((message.MessageNotificationClass == Message.NotificationClass.VISIT) || (message.MessageNotificationClass == Message.NotificationClass.TRANSIT)) { if ((message.InternalStatus == Message.BSMDStatus.UNDEFINED) || (message.InternalStatus == Message.BSMDStatus.PREPARE) || (message.InternalStatus == Message.BSMDStatus.TOSEND)) { _log.Debug("Visit/Transit not found, SENDING VISIT/TRANSIT message"); if (message.HIS == Message.NSWProvider.UNDEFINED) message.HIS = core.InitialHIS; if (core.DefaultReportingPartyId.HasValue) message.ReportingPartyId = core.DefaultReportingPartyId; toSendMessageList.Add(message); } } } } else // eine VISIT/TRANSIT Id ist vorhanden, die Daten können gesendet werden { // Änderung Sept 15: versendet werden alle Nachrichten, die *nicht* bestätigt sind // also auch alles was auf in bearbeitung / Korrektur etc steht // Einzig bleibt die Frage offen, was mit Nachrichten passiert, die über das UI // nachträglich geändert werden. Ich denke hier muss in dashface ggf. der Status // wieder auf PREPARE gesetzt werden _log.Debug("Visit/Transit found, SENDING DATA messages"); foreach (Message message in messages) { // Eine Nachricht mit dem Status "Suspended / Zurückgestellt" wird so lange nicht // versendet, bis im ENI der Status wieder explizit zurückgesetzt wird (16.4.16) // Update 5.10.17: Alles was nicht explizit auf "TOSEND" steht wird nicht verschickt! if (message.InternalStatus != Message.BSMDStatus.TOSEND) continue; // "virtuelle" Messages nicht versenden (DK, EU) if ((message.MessageNotificationClass == Message.NotificationClass.STO) || (message.MessageNotificationClass == Message.NotificationClass.CREWD) || (message.MessageNotificationClass == Message.NotificationClass.PASD)) continue; // Wenn das ein Transit ist, werden nicht erforderliche Meldeklassen übersprungen if (core.IsTransit) { if ((message.MessageNotificationClass == Message.NotificationClass.BKRD) || (message.MessageNotificationClass == Message.NotificationClass.PRE72H) || (message.MessageNotificationClass == Message.NotificationClass.TIEFD) || (message.MessageNotificationClass == Message.NotificationClass.NAME) || (message.MessageNotificationClass == Message.NotificationClass.INFO) || (message.MessageNotificationClass == Message.NotificationClass.ATA) || (message.MessageNotificationClass == Message.NotificationClass.ATD) || (message.MessageNotificationClass == Message.NotificationClass.LADG) || (message.MessageNotificationClass == Message.NotificationClass.SERV) || (message.MessageNotificationClass == Message.NotificationClass.WAS) || (message.MessageNotificationClass == Message.NotificationClass.TOWD)) continue; } // Visit/Transit Meldeklassen werden nicht erneut übertragen if ((message.MessageNotificationClass == Message.NotificationClass.VISIT) || (message.MessageNotificationClass == Message.NotificationClass.TRANSIT)) continue; //if ((message.InternalStatus != Message.BSMDStatus.CONFIRMED) && //(message.InternalStatus != Message.BSMDStatus.SENT)) //{ // 28.12.2015: Das über "Overview" eingestellte HIS ist immer "führend" (zumindest aktuell zum Testen) // if (message.HIS == Message.NSWProvider.UNDEFINED) message.HIS = core.InitialHIS; if (core.DefaultReportingPartyId.HasValue) { message.ReportingPartyId = core.DefaultReportingPartyId; // Referenz umbiegen if (DBManager.Instance.GetReportingPartyDict().ContainsKey(core.DefaultReportingPartyId.Value)) // geladenes Objekt ersetzen message.ReportingParty = DBManager.Instance.GetReportingPartyDict()[core.DefaultReportingPartyId.Value]; } toSendMessageList.Add(message); //} } } foreach (Message message in toSendMessageList) { try { _log.InfoFormat("Sending {0} message to {1}", message.MessageNotificationClass.ToString(), message.HIS.ToString()); sendSucceeded = false; // switch über passendes HIS / Schnittstelle switch (message.HIS) { case Message.NSWProvider.DBH: case Message.NSWProvider.DBH_TEST: if (!XtraSendLogic.ShouldSendMessage(message)) { message.InternalStatus = Message.BSMDStatus.SUSPENDED; } else { sendSucceeded = bsmd.dbh.Request.SendMessage(message, (message.HIS == Message.NSWProvider.DBH_TEST)); if (!sendSucceeded) message.InternalStatus = Message.BSMDStatus.SEND_FAILED; } break; case Message.NSWProvider.DAKOSY: case Message.NSWProvider.DAKOSY_TEST: sendSucceeded = bsmd.dakosy.Request.Send(message, true); if (!sendSucceeded) message.InternalStatus = Message.BSMDStatus.SEND_FAILED; break; default: _log.WarnFormat("Initial HIS not specified for message {0}", message.Id); break; } if (sendSucceeded) { // alte Fehlerliste entfernen (die Antwort kann praktisch noch nicht da sein) // vor dem Versenden zu entfernen halte ich für doof, wenn das Versenden scheitert foreach (MessageError messageError in message.ErrorList) DBManager.Instance.Delete(messageError); foreach (MessageViolation messageViolation in message.ViolationList) DBManager.Instance.Delete(messageViolation); _log.Info("send successful, saving message."); message.InternalStatus = Message.BSMDStatus.SENT; message.SentAt = DateTime.Now; } DBManager.Instance.Save(message); } catch (Exception ex) { _log.ErrorFormat("SENDING message {0}: {1}", message.Id.ToString(), ex.Message); } } if (toSendMessageList.Count > 0) { core.BSMDStatusInternal = MessageCore.BSMDStatus.SENT; } else { core.BSMDStatusInternal = MessageCore.BSMDStatus.PREPARE; } DBManager.Instance.Save(core); #endregion } } */ } bsmd.hisnord.transmitter.CallTransmitter(true); // bsmd.hisnord.transmitter.CallTransmitter(false); // wird aktuell nicht funktionieren // ob test oder nicht ist in stat. dict gespeicher bsmd.hisnord.Request.ReadResponseFiles(); bsmd.hisnord.Response.ReadAnswers(true); // bsmd.hisnord.Response.ReadAnswers(false); List coresMarkedForStatusQuery = DBManager.Instance.GetMessageCoresWithNSWStatusFlag(); foreach (MessageCore core in coresMarkedForStatusQuery) { core.QueryNSWStatus = false; // reset flag Status aStatus = new Status(core); aStatus.PerformQuery(); } DBManager.Instance.Disconnect(); } else { // _log.Fatal("database connection failure, stopping service"); this.EventLog.WriteEntry("NSW Send Service DB connection failure", EventLogEntryType.Warning); // this.Stop(); } lock (this._timerlock) { this.processRunning = false; } } protected override void OnPause() { this._timer.Stop(); } protected override void OnContinue() { this._timer.Start(); } protected override void OnStop() { this._timer.Enabled = false; this.EventLog.WriteEntry("NSW Send Service stopped.", EventLogEntryType.Information); _log.Info("NSW Send Service stopped"); } } }