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" ''' ''' Transfers a document to winline via Webservices ''' ''' ''' ''' ''' ''' ''' ''' ''' True if request was successful. 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