IF OBJECT_ID('auditlog.DialogPool') IS NOT NULL DROP TABLE [auditlog].[DialogPool]; GO CREATE TABLE [auditlog].[DialogPool] ( [FromService] SYSNAME NOT NULL, [ToService] SYSNAME NOT NULL, [OnContract] SYSNAME NOT NULL, [Handle] UNIQUEIDENTIFIER NOT NULL, [OwnerSPID] INT NOT NULL, [CreationTime] DATETIME NOT NULL, [SendCount] BIGINT NOT NULL, CONSTRAINT [UQ_DialogPool_Handle] UNIQUE (Handle) ); GO IF OBJECT_ID('auditlog.usp_get_dialog') IS NOT NULL DROP PROCEDURE [auditlog].[usp_get_dialog]; GO CREATE PROCEDURE [auditlog].[usp_get_dialog] @fromService SYSNAME, @toService SYSNAME, @onContract SYSNAME, @dialogHandle UNIQUEIDENTIFIER OUTPUT, @sendCount BIGINT OUTPUT AS BEGIN SET NOCOUNT ON; DECLARE @dialog TABLE ( [FromService] SYSNAME NOT NULL, [ToService] SYSNAME NOT NULL, [OnContract] SYSNAME NOT NULL, [Handle] UNIQUEIDENTIFIER NOT NULL, [OwnerSPID] INT NOT NULL, [CreationTime] DATETIME NOT NULL, [SendCount] BIGINT NOT NULL ); -- Try to claim an unused dialog in DialogPool -- READPAST option avoids blocking on locked dialogs. BEGIN TRANSACTION; DELETE FROM @dialog; UPDATE TOP(1) [auditlog].[DialogPool] WITH(READPAST) SET [OwnerSPID] = @@SPID OUTPUT INSERTED.* INTO @dialog WHERE [FromService] = @fromService AND [ToService] = @toService AND [OnContract] = @OnContract AND [OwnerSPID] = -1; IF @@ROWCOUNT > 0 BEGIN SET @dialogHandle = (SELECT [Handle] FROM @dialog); SET @sendCount = (SELECT [SendCount] FROM @dialog); END ELSE BEGIN -- No free dialogs: need to create a new one BEGIN DIALOG CONVERSATION @dialogHandle FROM SERVICE @fromService TO SERVICE @toService ON CONTRACT @onContract WITH ENCRYPTION = OFF; INSERT INTO [auditlog].[DialogPool] ([FromService], [ToService], [OnContract], [Handle], [OwnerSPID], [CreationTime], [SendCount]) VALUES (@fromService, @toService, @onContract, @dialogHandle, @@SPID, GETDATE(), 0); SET @sendCount = 0; END COMMIT TRANSACTION; END GO -------------------------------------------------------------------------- -- Free dialog procedure. -- Return the dialog to the pool. -- Inputs are dialog handle and updated send count. -------------------------------------------------------------------------- IF OBJECT_ID('auditlog.usp_free_dialog') IS NOT NULL DROP PROCEDURE [auditlog].[usp_free_dialog]; GO CREATE PROCEDURE [auditlog].[usp_free_dialog] @dialogHandle UNIQUEIDENTIFIER, @sendCount BIGINT AS BEGIN SET NOCOUNT ON; DECLARE @rowcount INT; DECLARE @string VARCHAR(50); BEGIN TRANSACTION; -- Release dialog by setting OwnerSPID to -1. UPDATE [auditlog].[DialogPool] SET [OwnerSPID] = -1, [SendCount] = @sendCount WHERE [Handle] = @dialogHandle; SET @rowcount = @@ROWCOUNT; IF @rowcount = 0 BEGIN SET @string = (SELECT CAST( @dialogHandle AS VARCHAR(50))); RAISERROR('usp_free_dialog: dialog %s not found in dialog pool', 16, 1, @string) WITH LOG; END ELSE IF @rowcount > 1 BEGIN SET @string = (SELECT CAST( @dialogHandle AS VARCHAR(50))); RAISERROR('usp_free_dialog: duplicate dialog %s found in dialog pool', 16, 1, @string) WITH LOG; END COMMIT TRANSACTION; END GO IF OBJECT_ID('auditlog.usp_delete_dialog') IS NOT NULL DROP PROCEDURE [auditlog].[usp_delete_dialog]; GO CREATE PROCEDURE [auditlog].[usp_delete_dialog] @dialogHandle UNIQUEIDENTIFIER AS BEGIN SET NOCOUNT ON; BEGIN TRY BEGIN TRANSACTION; DELETE [DialogPool] WHERE Handle = @dialogHandle; COMMIT TRANSACTION; END TRY BEGIN CATCH -- Rollback any active or uncommittable transactions before -- inserting information in the ErrorLog IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION; END EXECUTE [auditlog].[uspLogError]; END CATCH; END GO IF OBJECT_ID('auditlog.usp_send') IS NOT NULL DROP PROCEDURE [auditlog].[usp_send]; GO CREATE PROCEDURE [auditlog].[usp_send] @fromService SYSNAME, @toService SYSNAME, @onContract SYSNAME, @messageType SYSNAME, @messageBody NVARCHAR(MAX) WITH EXECUTE AS 'Kreta_tech_user' AS BEGIN SET NOCOUNT ON; DECLARE @dialogHandle UNIQUEIDENTIFIER; DECLARE @sendCount BIGINT; DECLARE @counter INT; DECLARE @error INT; SET @counter = 1; BEGIN TRANSACTION; -- message validation check, preventing poison message at the target service validation -- target service validates WELL_FORMED_XML types, so it is best to prevent it at here IF EXISTS (SELECT 1 FROM sys.[service_message_types] WHERE [name] = @messageType AND [validation] = 'X') AND (TRY_CONVERT(xml, @messageBody) IS NOT NULL) BEGIN SET @messageBody = CAST(TRY_CONVERT(xml, @messageBody) AS nvarchar(max)) END ELSE BEGIN INSERT INTO [auditlog].[AuditServiceFailedItems] ([conversation_handle], [message_body]) VALUES ('00000000-0000-0000-0000-000000000000', @messageBody) COMMIT TRANSACTION; RAISERROR('XML validation failure before sending. Check [auditlog].[AuditServiceFailedItems] for failed message.', 16,1) WITH LOG; END -- Will need a loop to retry in case the dialog is -- in a state that does not allow transmission WHILE (1=1) BEGIN -- Claim a dialog from the dialog pool. -- A new one will be created if none are available. EXEC [auditlog].[usp_get_dialog] @fromService, @toService, @onContract, @dialogHandle OUTPUT, @sendCount OUTPUT; -- Attempt to SEND on the dialog IF (@messageBody IS NOT NULL) BEGIN -- If the @messageBody is not null it must be sent explicitly SEND ON CONVERSATION @dialogHandle MESSAGE TYPE @messageType (@messageBody); END ELSE BEGIN -- Messages with no body must *not* specify the body, -- cannot send a NULL value argument SEND ON CONVERSATION @dialogHandle MESSAGE TYPE @messageType; END SET @error = @@ERROR; IF @error = 0 BEGIN -- Successful send, increment count and exit the loop SET @sendCount = @sendCount + 1; BREAK; END SET @counter = @counter + 1; IF @counter > 10 BEGIN -- We failed 10 times in a row, something must be broken RAISERROR('Failed to SEND on a conversation for more than 10 times. Error %i.', 16, 1, @error) WITH LOG; BREAK; END -- Delete the associated dialog from the table and try again EXEC [auditlog].[usp_delete_dialog] @dialogHandle; SELECT @dialogHandle = NULL; END -- "Criterion" for dialog pool removal is send count > 1000. -- Modify to suit application. -- When deleting also inform the target to end the dialog. IF @sendCount > 1000 BEGIN EXEC [auditlog].[usp_delete_dialog] @dialogHandle ; SEND ON CONVERSATION @dialogHandle MESSAGE TYPE [EndOfStream]; END ELSE BEGIN -- Free the dialog. EXEC [auditlog].[usp_free_dialog] @dialogHandle, @sendCount; END COMMIT TRANSACTION; END GO