Imports DigitalData.Modules.Database Imports DigitalData.Modules.Logging Imports DigitalData.Modules.Base Imports GdPicture14 Imports Quartz Imports System.Security.Cryptography Imports System.IO Imports EnvelopeGenerator.Common.Jobs.FinalizeDocument.FinalizeDocumentExceptions Imports EnvelopeGenerator.Common.Jobs.FinalizeDocument Imports System.Web.Caching Imports System.Web.UI Namespace Jobs Public Class FinalizeDocumentJob Implements IJob Private ReadOnly LicenseManager As New LicenseManager() Private GdViewer As GdViewer Private LogConfig As LogConfig Private Logger As Logger Private Database As MSSQLServer Private Config As DbConfig Private ConfigModel As ConfigModel Private EnvelopeModel As EnvelopeModel Private ReportModel As ReportModel Private ActionService As ActionService Private PDFBurner As PDFBurner Private ReportCreator As ReportCreator Private Class EnvelopeData Public EnvelopeId As Integer Public DocumentPath As String Public AnnotationData As List(Of String) End Class Public Function Execute(pContext As IJobExecutionContext) As Task Implements IJob.Execute Dim oGdPictureKey As String = pContext.MergedJobDataMap.Item(Constants.GDPICTURE) LogConfig = pContext.MergedJobDataMap.Item(Constants.LOGCONFIG) Logger = LogConfig.GetLogger() Dim JobId = pContext.JobDetail.Key Logger.Info("Starting job {0}", JobId) Try Logger.Debug("Loading GdViewer..") GdViewer = New GdViewer() LicenseManager.RegisterKEY(oGdPictureKey) Logger.Debug("Loading Database..") Database = GetDatabase(pContext, LogConfig) Logger.Debug("Loading Models & Services") Dim oState = GetState() InitializeModels(oState) InitializeServices(oState) Logger.Debug("Loading PDFBurner..") PDFBurner = New PDFBurner(LogConfig, oGdPictureKey) Logger.Debug("Loading ReportCreator..") ReportCreator = New ReportCreator(LogConfig, oState) Logger.Debug("Loading Configuration..") Config = ConfigModel.LoadConfiguration() Logger.Debug("DocumentPath: [{0}]", Config.DocumentPath) Logger.Debug("ExportPath: [{0}]", Config.ExportPath) Dim oCompleteStatus As Integer = Constants.EnvelopeStatus.EnvelopeCompletelySigned Dim oSql = $"SELECT * FROM TBSIG_ENVELOPE WHERE STATUS = {oCompleteStatus}" Dim oTable = Database.GetDatatable(oSql) Dim oEnvelopeIds As List(Of Integer) = oTable.Rows.Cast(Of DataRow). Select(Function(r) r.Item("GUID")). Cast(Of Integer). ToList() Logger.Info("Found [{0}] completed envelopes.", oEnvelopeIds.Count) Dim oTotal As Integer = oEnvelopeIds.Count Dim oCurrent As Integer = 1 For Each oId In oEnvelopeIds Logger.Info("Finalizing Envelope [{0}] ({1}/{2})", oId, oCurrent, oTotal) Logger.Debug("Loading Envelope..") Dim oEnvelope = EnvelopeModel.GetById(oId) If oEnvelope Is Nothing Then Logger.Warn("Envelope could not be loaded for Id [{0}]!", oId) Throw New ArgumentNullException("EnvelopeData") End If Logger.Debug("Loading Envelope Data..") Dim oEnvelopeData = GetEnvelopeData(oId) If oEnvelopeData Is Nothing Then Logger.Warn("EnvelopeData could not be loaded for Id [{0}]!", oId) Throw New ArgumentNullException("EnvelopeData") End If Logger.Debug("Burning Annotations to pdf") Dim oBurnedDocument As Byte() = BurnAnnotationsToPdf(oEnvelopeData) If oBurnedDocument Is Nothing Then Logger.Warn("Document could not be finalized!") Throw New ApplicationException("Document could not be finalized") End If Logger.Debug("Creating report..") Dim oReport As Byte() = ReportCreator.CreateReport(oEnvelope) Logger.Debug("Merging documents..") Dim oMergedDocument As Byte() = MergeDocuments(oBurnedDocument, oReport) Dim oOutputDirectoryPath = Config.ExportPath Dim oOutputFilePath = Path.Combine(oOutputDirectoryPath, $"{oEnvelope.Uuid}.pdf") Logger.Info("Writing finalized Pdf to disk..") Logger.Info("Output path is [{0}]", oOutputFilePath) Try File.WriteAllBytes(oOutputFilePath, oMergedDocument) Catch ex As Exception Throw New ExportDocumentException("Could not export final document to disk!", ex) Logger.Error(ex) End Try Logger.Debug("Setting envelope status..") If ActionService.FinalizeEnvelope(oEnvelope) = False Then Logger.Warn("Envelope could not be finalized!") Throw New ApplicationException("Envelope could not be finalized") End If oCurrent += 1 Logger.Info("Envelope finalized!") Next Logger.Info("Completed job {0} successfully!", JobId) Catch ex As MergeDocumentException Logger.Warn("Certificate Document job failed at step: Merging documents!") Logger.Error(ex) Catch ex As Exception Logger.Warn("Certificate Document job failed!") Logger.Error(ex) End Try Logger.Info("Job execution for [{0}] ended", JobId) End Function Private Function MergeDocuments(pDocument As Byte(), pReport As Byte()) As Byte() Using oDocumentStream As New MemoryStream(pDocument) Using oReportStream As New MemoryStream(pReport) Using oFinalStream As New MemoryStream() Using oDocumentPDF As New GdPicturePDF() Using oReportPDF As New GdPicturePDF() Dim oStatus As GdPictureStatus = GdPictureStatus.OK ' Load the source file into memory oDocumentPDF.LoadFromStream(oDocumentStream, True) oStatus = oDocumentPDF.GetStat() If oStatus <> GdPictureStatus.OK Then Throw New MergeDocumentException($"Document could not be loaded: {oStatus}") End If ' Load the report file into memory oReportPDF.LoadFromStream(oReportStream, True) oStatus = oReportPDF.GetStat() If oStatus <> GdPictureStatus.OK Then Throw New MergeDocumentException($"Report could not be loaded: {oStatus}") End If ' Merge the documents Dim oMergedPDF = oDocumentPDF.Merge2Documents(oDocumentPDF, oReportPDF) oStatus = oMergedPDF.GetStat() If oStatus <> GdPictureStatus.OK Then Throw New MergeDocumentException($"Documents could not be merged: {oStatus}") End If ' Convert to PDF/A oMergedPDF.ConvertToPDFA(oFinalStream, PdfConversionConformance.PDF_A_1b, True, True) oStatus = oDocumentPDF.GetStat() If oStatus <> GdPictureStatus.OK Then Throw New MergeDocumentException($"Document could not be converted to PDF/A: {oStatus}") End If Return oFinalStream.ToArray() End Using End Using End Using End Using End Using End Function Private Function BurnAnnotationsToPdf(pData As EnvelopeData) As Byte() Dim pEnvelopeId = pData.EnvelopeId Logger.Info("Burning [{0}] signatures", pData.AnnotationData.Count) Dim oAnnotations = pData.AnnotationData Dim oInputPath = pData.DocumentPath Logger.Info("Input path: [{0}]", oInputPath) Dim oInputDocumentBuffer As Byte() Try oInputDocumentBuffer = File.ReadAllBytes(oInputPath) Catch ex As Exception Logger.Error(ex) Throw New BurnAnnotationException("Source document could not be read from disk!", ex) End Try Return PDFBurner.BurnInstantJSONAnnotationsToPDF(oInputDocumentBuffer, oAnnotations) End Function Private Function GetEnvelopeData(pEnvelopeId As Integer) As EnvelopeData Dim oSql = $"SELECT T.GUID, T2.FILEPATH FROM [dbo].[TBSIG_ENVELOPE] T JOIN TBSIG_ENVELOPE_DOCUMENT T2 ON T.GUID = T2.ENVELOPE_ID WHERE T.GUID = {pEnvelopeId}" Dim oTable As DataTable = Database.GetDatatable(oSql) Dim oRow As DataRow = oTable.Rows.Cast(Of DataRow).SingleOrDefault() If oRow Is Nothing Then Return Nothing End If Dim oAnnotationData = GetAnnotationData(pEnvelopeId) Dim oData As New EnvelopeData With { .EnvelopeId = pEnvelopeId, .DocumentPath = oRow.ItemEx("FILEPATH", ""), .AnnotationData = oAnnotationData } Logger.Debug("Document path: [{0}]", oData.DocumentPath) Return oData End Function Private Function GetAnnotationData(pEnvelopeId As Integer) As List(Of String) Dim oSql = $"SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = {pEnvelopeId}" Dim oTable As DataTable = Database.GetDatatable(oSql) Return oTable.Rows.Cast(Of DataRow). Select(Function(r) r.ItemEx("VALUE", String.Empty)). Cast(Of String). ToList() End Function Private Sub InitializeServices(pState As State) ActionService = New ActionService(pState) End Sub Private Sub InitializeModels(pState As State) ConfigModel = New ConfigModel(pState) EnvelopeModel = New EnvelopeModel(pState) ReportModel = New ReportModel(pState) End Sub Private Function GetDatabase(pContext As IJobExecutionContext, pLogConfig As LogConfig) As MSSQLServer Dim oConnectionString As String = pContext.MergedJobDataMap.Item(Constants.DATABASE) Dim Database = New MSSQLServer(pLogConfig, MSSQLServer.DecryptConnectionString(oConnectionString)) Return Database End Function Private Function GetState() As State Return New State With { .LogConfig = LogConfig, .Database = Database, .UserId = 0, .Config = Nothing, .DbConfig = Nothing } End Function End Class End Namespace