// Copyright (c) 2015-2017 schick Informatik // Description: Bearbeitung von Antworten (dateibasiert..) using bsmd.database; using System; using System.Collections.Generic; using System.IO; using log4net; using System.Xml.Linq; namespace bsmd.hisnord { public static class Response { private static readonly ILog _log = LogManager.GetLogger(typeof(Response)); public static void ReadAnswers() { string answerDir = Path.Combine(Properties.Settings.Default.TransmitterRoot, Properties.Settings.Default.AnswerDir); foreach (string answerFile in Directory.GetFiles(answerDir)) { bool isOK = true; // bei Problemen die Antwort auf die Seite kopieren.. // File.Copy(answerFile, Path.Combine(@"C:\temp\hnord", Path.GetFileName(answerFile))); // Informationen aus dem Dateinamen // Meldetyp_Referenz_ID_Timestamp.xml string bareFileName = Path.GetFileNameWithoutExtension(answerFile); string[] fileNameElems = bareFileName.Split('_'); if (fileNameElems.Length < 4) { _log.WarnFormat("ANSWER file {0}.xml has an invalid file name", bareFileName); isOK = false; } else { if (!Int32.TryParse(fileNameElems[fileNameElems.Length - 1], out int prozessStatus)) { _log.WarnFormat("ANSWER file {0}.xml has no process status at the end (2..6)", bareFileName); isOK = false; } else { if (!Int64.TryParse(fileNameElems[fileNameElems.Length - 2], out long timestampMilliSecs)) { _log.WarnFormat("ANSWER file {0}.xml has no readable timestamp", bareFileName); isOK = false; } else { string refId = fileNameElems[fileNameElems.Length - 3]; string meldeTyp = fileNameElems[fileNameElems.Length - 4]; if (fileNameElems.Length == 5) meldeTyp = string.Format("{0}_{1}", fileNameElems[fileNameElems.Length - 5], meldeTyp); // TODO: klären was man hier liest: reguläre Antwort oder Schnittstellenfehler // XML Linq statt Serialisierung try { XElement xml = XElement.Load(answerFile); // declare Namespaces XNamespace ns1 = "http://api.national-single-window.de/visitIdRequest"; XNamespace ns6 = "http://api.national-single-window.de/receipt"; XNamespace ns14 = "http://api.national-single-window.de/statusForClientRequestId"; XNamespace soap = "http://schemas.xmlsoap.org/soap/envelope/"; XNamespace ns15 = "http://api.national-single-window.de/visitIdResponse"; if (xml.Name == "SystemError") { // Fehlernachricht SystemError systemError = SystemError.createFromXml(xml); if (systemError != null) { MessageCore aCore = DBManager.Instance.GetMessageCoreById(systemError.MessageCoreId); if (aCore != null) { if (Enum.TryParse(systemError.Meldetype, out Message.NotificationClass notificationClass)) { Message refMessage = DBManager.Instance.GetMessage(aCore, notificationClass); if (refMessage != null) { refMessage.InternalStatus = Message.BSMDStatus.SEND_FAILED; systemError.MessageHeaderId = refMessage.Id; DBManager.Instance.Save(refMessage); } } else { if (systemError.Meldetype.Equals("REQUEST_ID")) { // Request gescheitert Message refMessage = DBManager.Instance.GetMessage(aCore, aCore.IsTransit ? Message.NotificationClass.TRANSIT : Message.NotificationClass.VISIT); if (refMessage != null) { refMessage.InternalStatus = Message.BSMDStatus.SEND_FAILED; systemError.MessageHeaderId = refMessage.Id; DBManager.Instance.Save(refMessage); } } } _log.WarnFormat("SystemError received for Core [{0}], IMO {1} ETA {2}: {3} Class {4}", aCore.Id, aCore.IMO, aCore.ETADisplay, systemError.ErrorMessage, Enum.GetName(typeof(Message.NotificationClass), notificationClass)); // Nach einiger Diskussion (vs. "SENT" und "FAILURE" geht das hier jetzt auf RESPONDED) aCore.BSMDStatusInternal = MessageCore.BSMDStatus.RESPONDED; DBManager.Instance.Save(aCore); } else { _log.WarnFormat("SystemError received for unknown core {0}: {1}", systemError.MessageCoreId, systemError.ErrorMessage); } // trotzdem immer speichern DBManager.Instance.Save(systemError); } } else { // NSW Rückmeldung NSWResponse nswResponse = new NSWResponse(xml); // Rückmeldung auswerten if (nswResponse.MessageCoreId.HasValue) { MessageCore core = DBManager.Instance.GetMessageCoreById(nswResponse.MessageCoreId.Value); if (core != null) { List messages = DBManager.Instance.GetMessagesForCore(core, DBManager.MessageLoad.ALL); if (nswResponse.NotificationClass == Message.NotificationClass.VISIT) { if ((nswResponse.Status == "ACCEPTED") && !nswResponse.VisitId.IsNullOrEmpty()) { _log.InfoFormat("Setting Visit-Id to {0} Core {1}", nswResponse.VisitId, core.Id); core.VisitId = nswResponse.VisitId; } } if (nswResponse.NotificationClass == Message.NotificationClass.TRANSIT) { if ((nswResponse.Status == "ACCEPTED") && !nswResponse.TransitId.IsNullOrEmpty()) { _log.InfoFormat("Setting Transit-Id to {0} Core {1}", nswResponse.TransitId, core.Id); core.TransitId = nswResponse.TransitId; } } if (nswResponse.NotificationClass == Message.NotificationClass.STO) { _log.InfoFormat("NSWRESPONSE Cancel Visit/Transit Reply: {0} Cancel? {1}", nswResponse.Status, (core.Cancelled ?? false) ? "YES" : "NO" ); if ((nswResponse.Status == "ACCEPTED") && (core.Cancelled ?? false)) { core.BSMDStatusInternal = MessageCore.BSMDStatus.RESPONDED; _log.InfoFormat("Core cancel confirmed for {0}", core.DisplayId); } if ((nswResponse.Status == "REJECTED") && (core.Cancelled ?? false)) { core.Cancelled = false; // CANCEL fehlgeschlagen core.BSMDStatusInternal = MessageCore.BSMDStatus.FAILURE; _log.InfoFormat("Cancel rejected for {0}", core.DisplayId); } } bool aMessageStillInSENTstate = false; // now find the message that was meant.. foreach (Message aMessage in messages) { if (aMessage.MessageNotificationClass == nswResponse.NotificationClass) { if (nswResponse.Status != null) { aMessage.ReceivedAt = nswResponse.ReceiveAt; bool isAccepted = (nswResponse.Status == "ACCEPTED"); if (isAccepted) { aMessage.SendSuccess = true; aMessage.Status = Message.MessageStatus.ACCEPTED; aMessage.InternalStatus = Message.BSMDStatus.CONFIRMED; if (nswResponse.Violations.Count > 0) { aMessage.InternalStatus = Message.BSMDStatus.VIOLATION; aMessage.StatusInfo = "Violations reported"; } } else { aMessage.Status = Message.MessageStatus.REJECTED; aMessage.InternalStatus = Message.BSMDStatus.ERROR; aMessage.StatusInfo = "Errors reported"; } if (aMessage.Reset && nswResponse.IsReset && isAccepted) { _log.InfoFormat("Message {0} RESET confirmed", aMessage.Id); aMessage.SendSuccess = false; // bestätigter RESET setzt den grünen Buppel wieder zurück } if (nswResponse.IsReset && !aMessage.Reset) aMessage.Reset = nswResponse.IsReset; } else { _log.WarnFormat("Message status not found in ANSWER {0}", answerFile); isOK = false; } #region Error / Violation handling // "alte" Meldungen entfernen foreach (MessageError existingError in aMessage.ErrorList) DBManager.Instance.Delete(existingError); foreach (MessageViolation existingViolation in aMessage.ViolationList) DBManager.Instance.Delete(existingViolation); if (!nswResponse.Violations.IsNullOrEmpty()) aMessage.InternalStatus = Message.BSMDStatus.VIOLATION; foreach (MessageViolation messageViolation in nswResponse.Violations) { messageViolation.MessageHeaderId = aMessage.Id.Value; messageViolation.MessageHeader = aMessage; DBManager.Instance.Save(messageViolation); } if (!nswResponse.Errors.IsNullOrEmpty()) aMessage.InternalStatus = Message.BSMDStatus.ERROR; foreach (MessageError messageError in nswResponse.Errors) { messageError.MessageHeaderId = aMessage.Id.Value; messageError.MessageHeader = aMessage; DBManager.Instance.Save(messageError); } _log.InfoFormat("Saving Message {0} Status {1} InternalStatus {2}", aMessage.Id, aMessage.Status, aMessage.InternalStatus); DBManager.Instance.Save(aMessage); #endregion } if (aMessage.InternalStatus == Message.BSMDStatus.SENT) { aMessageStillInSENTstate = true; _log.InfoFormat("message {0} {1} still in SENT state", aMessage.Id, aMessage.MessageNotificationClassDisplay); } } if (!aMessageStillInSENTstate && !(core.Cancelled ?? false)) core.BSMDStatusInternal = MessageCore.BSMDStatus.RESPONDED; DBManager.Instance.Save(core); } else { _log.ErrorFormat("cannot find core for id {0}", nswResponse.MessageCoreId); isOK = false; } } else { _log.ErrorFormat("received response without suitable conveyance code, request id {0}", nswResponse.ClientRequestId); isOK = false; } } } catch (Exception ex) { _log.WarnFormat("Exception deserializing ANSWER file: {0}", ex.ToString()); isOK = false; } } } } if(isOK) { // archive file string answerArchiveDir = Path.Combine(Properties.Settings.Default.TransmitterRoot, Properties.Settings.Default.AnswerArchiveDir); try { File.Move(answerFile, Path.Combine(answerArchiveDir, Path.GetFileName(answerFile))); } catch(Exception ex) { _log.ErrorFormat("cannot move {0} to {1}:{2}", answerFile, answerArchiveDir, ex.Message); } } else { // save in separate folder (to look at it later?) string answerCorruptDir = Path.Combine(Properties.Settings.Default.TransmitterRoot, Properties.Settings.Default.AnswerCorruptDir); try { File.Move(answerFile, Path.Combine(answerCorruptDir, Path.GetFileName(answerFile))); } catch(Exception ex) { _log.ErrorFormat("cannot move {0} to {1}:{2}", answerFile, answerCorruptDir, ex.Message); } } } } } }