MultiTool/MultiTool.Common/Winline/WebServiceData.vb
2023-09-13 16:13:10 +02:00

564 lines
24 KiB
VB.net

Imports System.Collections.Specialized
Imports System.Net.Http
Imports System.Text
Imports System.Xml
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.Logging
Imports MultiTool.Common.Documents
Imports MultiTool.Common.Exceptions
Imports MultiTool.Common.Templates
Imports MultiTool.Common.Templates.GeneralConfig
Imports MultiTool.Common.Winline.Entities
Namespace Winline
Public Class WebServiceData
Inherits BaseClass
Private ReadOnly Config As WebServiceConfig
Private ReadOnly Filters As FilterConfig
Private ReadOnly Serializer As Serializer
Private ReadOnly Winline As WinlineData
Private ReadOnly FileEx As FilesystemEx
Private ReadOnly Patterns As Patterns
Public Event WebServiceProgress As EventHandler(Of String)
Public Sub New(pLogConfig As LogConfig, pDatabase As MSSQLServer, pWinline As WinlineData, pWebserviceConfig As WebServiceConfig, pGeneralConfig As GeneralConfig, pFilterConfig As FilterConfig)
MyBase.New(pLogConfig, pDatabase)
Serializer = New Serializer(pLogConfig)
Config = pWebserviceConfig
Filters = pFilterConfig
Patterns = New Patterns(pLogConfig, pGeneralConfig)
FileEx = New FilesystemEx(LogConfig)
Winline = pWinline
End Sub
Public Sub RaiseWebServiceProgress(pMessage As String)
RaiseEvent WebServiceProgress(Me, pMessage)
End Sub
#Region "Import"
''' <summary>
''' Transfers a document to winline via Webservices
''' </summary>
''' <param name="pDocument"></param>
''' <param name="pTemplate"></param>
''' <param name="pMandator"></param>
''' <param name="pIsTest"></param>
''' <exception cref="HttpRequestException"></exception>
''' <exception cref="WebServiceException"></exception>
''' <exception cref="TaskCanceledException"></exception>
''' <returns>True if request was successful.</returns>
Public Async Function TransferDocumentToWinline(pDocument As Documents.Document, pTemplate As Template, pMandator As Mandator, Optional pIsTest As Boolean = False) As Task(Of Boolean)
Dim oWS = Config
RaiseEvent WebServiceProgress(Me, "Einstellungen laden")
Logger.Debug("Loading setting and creating directories")
' --- Build all teh filenamez and pathz
Dim oBaseFileName As String = FileEx.GetDateTimeString()
Dim oFileName = $"{pTemplate.Name}-{oBaseFileName}-Request.xml"
' --- Get and create path for request/response files
Dim oOutputDirectory = FileEx.CreateDateDirectory(pTemplate.OutputWebserviceDirectory)
Dim oOutputFilePath = IO.Path.Combine(oOutputDirectory, oFileName)
' Generate absolute path to copy xml file to
Dim oAbsolutePath = IO.Path.Combine(oWS.ImportBasePath, oWS.ImportRelativePath)
oAbsolutePath = FileEx.CreateDateDirectory(oAbsolutePath)
Dim oImportAbsoluteFilePath = IO.Path.Combine(oAbsolutePath, oFileName)
' Generate relative path to supply to winline
Dim oRelativePath = IO.Path.Combine(oWS.ImportRelativePath)
oRelativePath = FileEx.GetDateDirectory(oRelativePath)
Dim oImportRelativeFilePath = IO.Path.Combine(oRelativePath, oFileName)
RaiseEvent WebServiceProgress(Me, "Dateien schreiben")
Logger.Debug("Writing request file to [{oOutputFilePath}]")
' If configured, prevent printing the document and instead create it as a draft in the "Nicht gedruckt" section
If pTemplate.PrintDocument = False Then
pDocument.PrintVoucher = 0
End If
' --- Serialize Data into XML string
Dim oBytes As Byte() = GetBytesFromDocument(pDocument)
IO.File.WriteAllBytes(oOutputFilePath, oBytes)
' --- Copy file to Winline Import Directory
Try
Logger.Debug("Copying request file to [{oImportAbsoluteFilePath}]")
IO.File.Copy(oOutputFilePath, oImportAbsoluteFilePath, True)
Catch ex As Exception
Logger.Error(ex)
Throw ex
End Try
' --- Prepare URL and HTTP Client
Dim oTemplateType = pDocument.TemplateType
Dim oTemplateName = pDocument.TemplateName
Logger.Debug("Using Template [{0}/{1}]", oTemplateName, oTemplateType)
' ActionCode: Should this be a test or not?
' 0 = Test call
' 1 = Real call
Dim oActionCode = 1
If pIsTest = True Then
oActionCode = 0
End If
Logger.Debug("Using ActionCode [{0}]", oActionCode)
' Byref: Should data be supplied as file or as string?
' 0 = As String
' 1 = As File (relative to Winline Server directory)
Dim oByref = 1
Dim oParams As New NameValueCollection() From {
{"User", oWS.Username},
{"Password", oWS.Password},
{"Company", pMandator.Id},
{"Type", oTemplateType},
{"Vorlage", oTemplateName},
{"ActionCode", oActionCode},
{"Byref", oByref},
{"Data", oImportRelativeFilePath}
}
Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/import{ToQueryString(oParams)}"
'Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/import?User={oWS.Username}&Password={oWS.Password}&Company={pMandator.Id}&Type={oTemplateType}&Vorlage={oTemplateName}&ActionCode={oActionCode}&Byref={oByref}&Data={oImportRelativeFilePath}"
Dim oClient As New HttpClient With {
.Timeout = TimeSpan.FromSeconds(Constants.HTTP_REQUEST_TIMEOUT_IN_SECONDS)
}
Logger.Info("Creating HTTP Request to [{0}]", oWS.BaseUrl)
RaiseEvent WebServiceProgress(Me, "Anfrage absenden")
' --- Bring the action!
Try
Dim oResponse As HttpResponseMessage = Await oClient.GetAsync(oURL)
Logger.Debug("HTTP Response recevied!")
Await HandleImportResponse(oResponse, pTemplate, oBaseFileName)
Return True
Catch ex As Exception
Logger.Error(ex)
Throw ex
Finally
oClient.Dispose()
End Try
End Function
Private Async Function HandleImportResponse(pResponse As HttpResponseMessage, pTemplate As Template, pBaseFileName As String) As Task
pResponse.EnsureSuccessStatusCode()
Dim oResponseBody As String = Await pResponse.Content.ReadAsStringAsync()
Dim oContentType = pResponse.Content.Headers.ContentType.MediaType
Dim oSerializer = Serializer.GetSerializer(GetType(Templates.Entities.MESOWebServiceResult))
Dim oOutputDirectory = FileEx.CreateDateDirectory(pTemplate.OutputWebserviceDirectory)
RaiseEvent WebServiceProgress(Me, "Antwort verarbeiten")
Logger.Debug("Response ContentType: [{0}]", oContentType)
Select Case oContentType
Case "text/xml"
Logger.Debug("Got XML Response. Checking.")
WriteResponseFile(oOutputDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.xml")
Dim oBytes As Byte() = Encoding.UTF8.GetBytes(oResponseBody)
Using oStream As New IO.MemoryStream(oBytes)
Dim oResponseObject As Templates.Entities.MESOWebServiceResult = oSerializer.Deserialize(oStream)
Dim oErrorStrings As New List(Of String)
If oResponseObject.ResultDetails IsNot Nothing Then
For Each oDetails As Templates.Entities.MESOWebServiceResultResultDetails In oResponseObject.ResultDetails
If oDetails.Success = True Then
Logger.Debug("KeyValue: [{0}]", oDetails.KeyValue)
Logger.Debug("VoucherNumber: [{0}]", oDetails.VoucherNumber)
Else
Logger.Warn("ErrorCode: [{0}]", oDetails.ErrorCode)
Logger.Warn("ErrorText: [{0}]", oDetails.ErrorText)
oErrorStrings.Add($"[{oDetails.ErrorCode}] {oDetails.ErrorText}")
End If
Next
End If
If oResponseObject.OverallSuccess = False Then
Dim oMessage = $"Request to Webservice was unsuccessful:{vbNewLine}{vbNewLine}{String.Join(vbNewLine, oErrorStrings.ToArray)}"
Logger.Warn("Overall Success was [false]")
Throw New WebServiceException(oMessage)
End If
End Using
Case "text/html"
Logger.Debug("Got TEXT/HTML Response. Throwing.")
WriteResponseFile(oOutputDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.xml")
Throw New WebServiceException(oResponseBody)
Case Else
Logger.Debug("Unknown Response ContentType: [{0}]", oContentType)
Throw New WebServiceException(oResponseBody)
End Select
End Function
Private Function GetBytesFromDocument(pDocument As Documents.Document) As Byte()
Using oStream As New IO.MemoryStream()
Dim w = XmlWriter.Create(oStream)
w.WriteStartDocument()
w.WriteStartElement("MESOWebService")
w.WriteAttributeString("Template", pDocument.TemplateName)
w.WriteAttributeString("TemplateType", pDocument.TemplateType)
w.WriteAttributeString("option", pDocument.Option)
w.WriteAttributeString("printVoucher", pDocument.PrintVoucher)
pDocument.Rows.Sort()
For Each oRow In pDocument.Rows
w.WriteStartElement(oRow.TableName)
For Each oField As KeyValuePair(Of String, DocumentRow.FieldValue) In oRow.Fields
If oField.Value.Final = String.Empty Then
Continue For
End If
If oField.Value.IsVirtual Then
Continue For
End If
w.WriteStartElement(oField.Key)
w.WriteValue(oField.Value.Final)
w.WriteEndElement() ' Field
Next
w.WriteEndElement() ' Row
Next
w.WriteEndElement() ' MESOWebService
w.WriteEndDocument() ' Document
w.Close()
Return oStream.ToArray()
End Using
End Function
#End Region
#Region "Export"
Async Function ExportDocumentFromWinline(pDocument As ExportDocument, pTemplate As Template, pMandator As Mandator, Optional pIsTest As Boolean = False) As Task(Of Boolean)
Dim oWS = Config
Logger.Info("Exporting document from Winline")
' --- Build all teh filenamez and pathz
Dim oBaseFileName As String = FileEx.GetDateTimeString()
Dim oFileName = $"{pTemplate.Name}-{oBaseFileName}.xml"
'Dim oFileName = FileEx.GetFilenameWithPrefix(oBaseFileName, pTemplate.Name, "xml")
Logger.Info("Filename will be [{0}]", oFileName)
' Save the filename to the document
pDocument.FilenameExport = oFileName
RaiseEvent WebServiceProgress(Me, "Abfrage vorbereiten")
Logger.Debug("Preparing WebService call")
' --- Prepare URL and HTTP Client
Dim oTemplateType = 30
Dim oTemplateName = pDocument.Schema.Name
Dim oKey = $"{pDocument.Account.Id}-{pDocument.RunningNumber}"
' ActionCode: Should this be a test or not?
' 0 = Test call
' 1 = Real call
Dim oActionCode = 1
If pIsTest = True Then
oActionCode = 0
End If
Logger.Debug("This is a test: [{0}]", pIsTest)
Dim oURL As String = $"{oWS.BaseUrl}/ewlservice/export?User={oWS.Username}&Password={oWS.Password}&Company={pMandator.Id}&Type={oTemplateType}&Vorlage={oTemplateName}&ActionCode={oActionCode}&Key={oKey}"
Dim oClient As New HttpClient()
Logger.Info("Creating HTTP Request to [{0}]", oWS.BaseUrl)
RaiseEvent WebServiceProgress(Me, "Anfrage absenden")
' --- Bring the action!
Try
Dim oResponse As HttpResponseMessage = Await oClient.GetAsync(oURL)
Logger.Info("Request ended with code: [{0}]", oResponse.StatusCode)
Await HandleExportResponse(oResponse, pDocument, pTemplate, pMandator, oBaseFileName)
Return True
Catch ex As Exception
Logger.Error(ex)
Throw ex
Finally
oClient.Dispose()
End Try
End Function
Private Async Function HandleExportResponse(pResponse As HttpResponseMessage, pDocument As Entities.ExportDocument, pTemplate As Template, pMandator As Mandator, pBaseFileName As String) As Task
pResponse.EnsureSuccessStatusCode()
Dim oResponseBody As String = Await pResponse.Content.ReadAsStringAsync()
Dim oContentType = pResponse.Content.Headers.ContentType.MediaType
Dim oSerializer = Serializer.GetSerializer(GetType(Templates.Entities.MESOWebServiceResult))
RaiseEvent WebServiceProgress(Me, "Antwort verarbeiten")
Logger.Debug("Processing response with type '{0}'", oContentType)
Select Case oContentType
Case "text/xml"
Dim oXmlResponse = oResponseBody
Dim oDoc = ConvertStringToDocument(oXmlResponse)
Logger.Debug("Applying Item Filters")
oDoc = ApplyItemFiltersForExport(pDocument, pTemplate, pMandator, oDoc)
Logger.Debug("Applying Item Functions")
oDoc = ApplyItemFunctionsForExport(pDocument, pTemplate, pMandator, oDoc)
Dim oXml = ConvertDocumentToString(oDoc)
' Webservice
WriteResponseFile(pTemplate.OutputWebserviceDirectory, oXml, $"{pTemplate.Name}-{pBaseFileName}-Response.xml")
' XML
WriteResponseFile(pTemplate.OutputXmlFileDirectory, oXml, $"{pTemplate.Name}-{pBaseFileName}.xml")
' Archive
WriteResponseFile(FileEx.CreateDateDirectory(pTemplate.ArchiveDirectory), oXml, $"{pTemplate.Name}-{pBaseFileName}.xml")
Case "text/html"
WriteResponseFile(pTemplate.OutputWebserviceDirectory, oResponseBody, $"{pTemplate.Name}-{pBaseFileName}-Response.txt")
Throw New ApplicationException(oResponseBody)
Case Else
Throw New ApplicationException(oResponseBody)
End Select
End Function
Private Function ConvertStringToDocument(pXmlString As String) As XmlDocument
Dim oDoc As New XmlDocument()
oDoc.LoadXml(pXmlString)
Return oDoc
End Function
Private Function ConvertDocumentToString(pXmlDocument As XmlDocument) As String
Dim oArray As Byte()
Using oStream As New IO.MemoryStream
pXmlDocument.Save(oStream)
oArray = oStream.ToArray()
End Using
Return System.Text.Encoding.UTF8.GetString(oArray)
End Function
Private Function ApplyItemFunctionsForExport(pDocument As Entities.ExportDocument, pTemplate As Template, pMandator As Mandator, oXMLDocument As XmlDocument) As XmlDocument
For Each oTable In pTemplate.Tables
Logger.Debug("Processing Table [{0}]", oTable.Name)
For Each oColumn As Template.Column In oTable.Columns
Dim oTableName As String = oTable.Name
Dim oItemName As String = oColumn.Name
Logger.Debug("Processing item [{0}]", oItemName)
For Each oFunction As FieldConfig.ColumnFunction In oColumn.Config.Functions
Dim oFunctionName = oFunction.Name
Dim oFunctionParams = oFunction.Params
Dim oPath = $"//MESOWebService/{oTableName}/{oItemName}"
Dim oNodes As XmlNodeList = oXMLDocument.SelectNodes(oPath)
Logger.Debug("Calling function [{0}] on node [{1}]", oFunctionName, oPath)
For Each oNode As XmlNode In oNodes
If oFunctionName = Constants.FUNCTION_GLN Then
Dim oGLN = Winline.TryGetGLN(oNode.InnerText, pMandator)
If oGLN Is Nothing Then
Throw New MissingAttributeException(Constants.FUNCTION_GLN)
End If
oNode.InnerText = oGLN
ElseIf oFunctionName = Constants.FUNCTION_EAN Then
Dim oEAN = Winline.TryGetEAN(oNode.InnerText, pMandator)
If oEAN Is Nothing Then
' 21.04.2022: Relax the EAN Check
' Since it is possible to have articles without a proper EAN in export,
' we dont throw here, but leave the original value in case of a failure.
' Throw New Exceptions.MissingAttributeException("EAN")
Logger.Warn("EAN could not be retrieved for Node {0}. Skipping.", oNode.Name)
Continue For
End If
oNode.InnerText = oEAN
ElseIf oFunctionName = Constants.FUNCTION_SQL Then
Dim oSQL = Patterns.ReplaceForExport(pDocument, pMandator, oFunctionParams)
Dim oValue = Database.GetScalarValue(oSQL)
If oValue Is Nothing Then
Throw New MissingAttributeException(Constants.FUNCTION_SQL)
End If
oNode.InnerText = oValue
End If
Next
Next
Next
Next
Return oXMLDocument
End Function
Private Function ApplyItemFiltersForExport(pDocument As ExportDocument, pTemplate As Template, pMandator As Mandator, oXMLDocument As XmlDocument) As XmlDocument
Dim oTableNames = pDocument.Schema.Tables.
Select(Function(table) table.Name).
ToList()
Dim oFilters = Filters.Items.
Where(Function(filter) oTableNames.Contains(filter.TableName)).
ToList()
Logger.Info("Applying [{0}] filters before exporting.", oFilters.Count)
For Each oFilter As FilterConfigItem In oFilters
Dim oTableName = oFilter.TableName
Logger.Debug("Applying filter for Table [{0}]", oTableName)
If String.IsNullOrEmpty(oFilter.SQLCommand) Then
Logger.Warn("SQL Command for filter is empty. Continuing.")
Continue For
End If
Logger.Debug("Executing SQL Command: [{0}]", oFilter.SQLCommand)
Dim oSQLResult = Database.GetDatatable(oFilter.SQLCommand)
If oSQLResult Is Nothing Then
Logger.Warn("SQL Command for filter returned nothing: {0}. Continuing.", oFilter.SQLCommand)
Continue For
End If
Dim oResultList = oSQLResult.AsEnumerable.
Select(Function(row) row.ItemEx(0, String.Empty).Trim).
ToList()
Logger.Debug("Filterlist contains [{0}] items", oResultList.Count)
Dim oTableNodes = oXMLDocument.SelectNodes($"//{oTableName}")
Logger.Debug("Table contains [{0}] elements", oTableNodes.Count)
For Each oElementNode As XmlNode In oTableNodes
Dim oPath As String = $"./{oFilter.ColumnName}"
Dim oSubNode = oElementNode.SelectSingleNode(oPath)
If oSubNode Is Nothing Then
Logger.Warn("Column [{0}] was not found in element.", oFilter.ColumnName)
Continue For
End If
Dim oNodeValue = oSubNode.InnerText.Trim
Logger.Debug("Element Value is [{0}]", oNodeValue)
If Not oResultList.Contains(oNodeValue) Then
Logger.Warn("Element for Column [{0}] was not found in filterlist.", oFilter.ColumnName)
Continue For
End If
Logger.Info("Removing node [{0}] containing value [{1}]", oPath, oNodeValue)
oElementNode.ParentNode.RemoveChild(oElementNode)
Next
Next
Return oXMLDocument
End Function
#End Region
Private Function WriteResponseFileWithSuffix(pPath As String, pBaseFileName As String, pResponseBody As String, pExtension As String, pSuffix As String) As Boolean
Try
Dim oRequestFileName As String = FileEx.GetFilenameWithSuffix(pBaseFileName, pSuffix, pExtension)
Dim oFilePath As String = IO.Path.Combine(pPath, oRequestFileName)
IO.File.WriteAllText(oFilePath, pResponseBody)
Return True
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
Private Function WriteResponseFileWithPrefix(pPath As String, pBaseFileName As String, pResponseBody As String, pExtension As String, pPrefix As String) As Boolean
Try
Dim oRequestFileName As String = FileEx.GetFilenameWithPrefix(pBaseFileName, pPrefix, pExtension)
Dim oFilePath As String = IO.Path.Combine(pPath, oRequestFileName)
IO.File.WriteAllText(oFilePath, pResponseBody)
Return True
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
Private Function WriteResponseFile(pPath As String, pResponseBody As String, pFileName As String) As Boolean
Try
Dim oFilePath As String = IO.Path.Combine(pPath, pFileName)
IO.File.WriteAllText(oFilePath, pResponseBody)
Return True
Catch ex As Exception
Logger.Error(ex)
Return False
End Try
End Function
Private Function ToQueryString(ByVal nvc As NameValueCollection) As String
Dim sb As StringBuilder = New StringBuilder("?")
Dim first As Boolean = True
For Each key As String In nvc.AllKeys
For Each value As String In nvc.GetValues(key)
If Not first Then
sb.Append("&")
End If
sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value))
first = False
Next
Next
Return sb.ToString()
End Function
End Class
End Namespace