325 lines
19 KiB
C#
325 lines
19 KiB
C#
// 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;
|
|
|
|
// 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<Message.NotificationClass>(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<Message> 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|