maandag 5 maart 2012

BizTalk 2009 Archive Incoming Messages

At one of our customers we want to be able to archive all messages that come in to BizTalk 2009. I created a pipeline component that does this, where archiving can be turned on or off from the BizTalk Administrator Console. Here is the code to do this.

using System;
using System.IO;
using System.Text;
using System.ComponentModel;
using Microsoft.BizTalk.Component.Interop;
using Microsoft.BizTalk.Message.Interop;

namespace Company.BizTalk.PipelineComponents
{
    /// <summary>
    /// Archive an incoming message.
    /// </summary>
    [ComponentCategory(CategoryTypes.CATID_PipelineComponent)]
    [System.Runtime.InteropServices.Guid("CFBF459C-01FC-49B8-903B-2DE65A000FF6")]
    [ComponentCategory(CategoryTypes.CATID_Decoder)]
    public class ArchivingComponentIncoming : 
        Microsoft.BizTalk.Component.Interop.IComponent, IBaseComponent, 
        IPersistPropertyBag, IComponentUI
    {

        #region Properties

        /// <summary>
        /// Boolean indicating if we want to archive the messages coming through this 
        /// pipeline.
        /// </summary>
        private bool _doArchiving;

        /// <summary>
        /// Boolean indicating if we want to archive the messages coming through this 
        /// pipeline.
        /// </summary>
        public bool DoArchiving
        {
            get
            {
                return _doArchiving;
            }
            set
            {
                _doArchiving = value;
            }
        }

        /// <summary>
        /// Location where we want to archive the messages.
        /// </summary>
        private string _archivePath;

        /// <summary>
        /// Location where we want to archive the messages.
        /// </summary>
        public string ArchivePath
        {
            get
            {
                return _archivePath;
            }
            set
            {
                _archivePath = value;
            }
        }

        #endregion


        #region IBaseComponent members
        
        /// <summary>
        /// Name of the component
        /// </summary>
        [Browsable(false)]
        public string Name
        {
            get
            {
                return "ArchivingComponentIncoming";
            }
        }

        /// <summary>
        /// Version of the component
        /// </summary>
        [Browsable(false)]
        public string Version
        {
            get
            {
                return "1.0.0.0";
            }
        }

        /// <summary>
        /// Description of the component
        /// </summary>
        [Browsable(false)]
        public string Description
        {
            get
            {
                return "Component used to archive incoming messages.";
            }
        }

        #endregion

        #region IPersistPropertyBag members
        
        /// <summary>
        /// Gets class ID of component for usage from unmanaged code.
        /// </summary>
        /// <param name="classid">
        /// Class ID of the component
        /// </param>
        public void GetClassID(out System.Guid classid)
        {
            classid = new System.Guid("CFBF459C-01FC-49B8-903B-2DE65A000FF6");
        }

        /// <summary>
        /// Not needed.
        /// </summary>
        public void InitNew()
        {
        }

        /// <summary>
        /// Loads configuration properties for the component.
        /// </summary>
        /// <param name="pb">Configuration property bag.</param>
        /// <param name="errlog">Error status.</param>
        public virtual void Load(Microsoft.BizTalk.Component.Interop.IPropertyBag pb,
            int errlog)
        {
            object val = null;
            
            // Get configuration property for indication if we want to archive
            val = this.ReadPropertyBag(pb, "DoArchiving");
            
            if (val != null)
            {
                this._doArchiving = ((bool)(val));
            }
            
            // Get configuration property for path where we want to archive
            val = this.ReadPropertyBag(pb, "ArchivePath");
            
            if (val != null)
            {
                this._archivePath = ((string)(val));
            }
        }

        /// <summary>
        /// Saves the current component configuration into the property bag.
        /// </summary>
        /// <param name="pb">Configuration property bag.</param>
        /// <param name="fClearDirty">Not used.</param>
        /// <param name="fSaveAllProperties">Not used.</param>
        public virtual void Save(Microsoft.BizTalk.Component.Interop.IPropertyBag pb,
            bool fClearDirty, bool fSaveAllProperties)
        {
            this.WritePropertyBag(pb, "DoArchiving", this.DoArchiving);

            this.WritePropertyBag(pb, "ArchivePath", this.ArchivePath);
        }

        #region utility functionality
        
        /// <summary>
        /// Reads property value from property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <returns>Value of the property.</returns>
        private object ReadPropertyBag(
            Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName)
        {
            object val = null;
            
            try
            {
                pb.Read(propName, out val, 0);
            }
            catch (System.ArgumentException)
            {
                return val;
            }
            catch (System.Exception e)
            {
                throw new System.ApplicationException(e.Message);
            }
            
            return val;
        }

        /// <summary>
        /// Writes property values into a property bag.
        /// </summary>
        /// <param name="pb">Property bag.</param>
        /// <param name="propName">Name of property.</param>
        /// <param name="val">Value of property.</param>
        private void WritePropertyBag(
            Microsoft.BizTalk.Component.Interop.IPropertyBag pb, string propName, 
            object val)
        {
            try
            {
                pb.Write(propName, ref val);
            }
            catch (System.Exception e)
            {
                throw new System.ApplicationException(e.Message);
            }
        }

        #endregion
        #endregion

        #region IComponentUI members
        /// <summary>
        /// Component icon to use in BizTalk Editor.
        /// </summary>
        [Browsable(false)]
        public IntPtr Icon
        {
            get
            {
                return IntPtr.Zero;
            }
        }

        /// <summary>
        /// The Validate method is called by the BizTalk Editor during the build of 
        /// a BizTalk project.
        /// </summary>
        /// <param name="obj">An Object containing the configuration properties.
        /// </param>
        /// <returns>The IEnumerator enables the caller to enumerate through a 
        /// collection of strings containing error messages. 
        /// These error messages appear as compiler error messages. 
        /// To report successful property validation, the method should return 
        /// an empty enumerator.</returns>
        public System.Collections.IEnumerator Validate(object obj)
        {
            return null;
        }

        #endregion

        #region IComponent members
        
        /// <summary>
        /// Implements IComponent.Execute method.
        /// </summary>
        /// <param name="pc">Pipeline context.</param>
        /// <param name="inmsg">Input message.</param>
        /// <returns>Original input message.</returns>
        /// <remarks>
        /// IComponent.Execute method is used to initiate the processing of the 
        /// message in this pipeline component.
        /// </remarks>
        public Microsoft.BizTalk.Message.Interop.IBaseMessage Execute(
            Microsoft.BizTalk.Component.Interop.IPipelineContext pc, 
            Microsoft.BizTalk.Message.Interop.IBaseMessage inmsg)
        {
            // Check if we want to archive the message
            if (_doArchiving)
            {
                // Get the message that was received
                IBaseMessage passedMessage = inmsg;

                // The name under which we want to archive the message
                string archiveFileName = null;

                // Get the interchange id from the message
                string interchangeID = (string)passedMessage.Context.Read(
                    "InterchangeID", 
                    "http://schemas.microsoft.com/BizTalk/2003/system-properties");

                // If the transport type if file or ftp, get the incoming filename 
                // to use as part of the archive filename (for easier identification)
                string filePath = null;

                // Get the type of adapter that was used for receiving the file
                string adapterType = (string)passedMessage.Context.Read(
                    "InboundTransportType", 
                    "http://schemas.microsoft.com/BizTalk/2003/system-properties");

                // Check if we received over a file adapter
                if (adapterType == "FILE")
                {
                    filePath = (string)passedMessage.Context.Read("ReceivedFileName",
                        "http://schemas.microsoft.com/BizTalk/2003/file-properties");
                }

                // Check if we received over a ftp adapter
                else if (adapterType == "FTP")
                {
                    filePath = (string)passedMessage.Context.Read("ReceivedFileName",
                        "http://schemas.microsoft.com/BizTalk/2003/ftp-properties");
                }

                // Set the name under which we want to archive the message
                archiveFileName = interchangeID + ".out";

                // Check if filePath was set
                if (filePath != null)
                {
                    // We ran into problems where the pipeline could not be executed,
                    // and the file just kept being archived
                    // To make sure this does not happen, we decided to never 
                    // archive the same message more then 100 times (still allowing 
                    // for multiple tests with the same message)
                    if (Directory.GetFiles(this._archivePath, String.Format("{0}*", 
                        Path.GetFileName(filePath))).Length > 100)
                    {
                        goto Finished;
                    }

                    // If filepath was set, add the incoming file name to the file 
                    // name under which we want to archive
                    archiveFileName = String.Format("{0}_{1}", 
                        Path.GetFileName(filePath), archiveFileName);
                }

                // Write the archive file
                WriteToFile(passedMessage, Path.Combine(this._archivePath, 
                    archiveFileName));
            }

            Finished:

            // Continue processing the message
            return inmsg;
        }

        /// <summary>
        /// This method is used to copy the contents from a memory stream to a
        /// filestream, saving the contents to a file.
        /// </summary>
        /// <param name="input">The memory stream.</param>
        /// <param name="output">The file stream.</param>
        protected void CopyStream(Stream input, Stream output)
        {
            // Set the buffer size for reading the stream
            int BUFFER_SIZE = 4096;

            // Create a buffer
            byte[] buffer = new byte[BUFFER_SIZE];

            // Integer indicating how many bytes we have read
            int bytesRead;
            
            try
            {
                // Read from the input stream
                bytesRead = input.Read(buffer, 0, BUFFER_SIZE);

                // Read all the bytes in the input stream
                while (bytesRead > 0)
                {
                    // Write the contents to the output stream
                    output.Write(buffer, 0, bytesRead);

                    // Go to the next byte
                    bytesRead = input.Read(buffer, 0, BUFFER_SIZE);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                // Rewind input stream
                if (input.CanSeek)
                {
                    input.Position = 0;
                }
            }
        }

        /// <summary>
        /// Write the contents of the message to an archive file.
        /// </summary>
        /// <param name="message">The message that should be archived.</param>
        /// <param name="fileName">The filename for the archive message.</param>
        protected void WriteToFile(IBaseMessage message, string fileName)
        {
            // Get the body of the message
            Stream msgStream = message.BodyPart.GetOriginalDataStream();

            // Create a new file stream
            FileStream fileStream = null;

            try
            {
                // Open the archive file for writing
                fileStream = new FileStream(fileName, FileMode.OpenOrCreate);

                // Copy the data from the message to the archive file
                this.CopyStream(msgStream, fileStream);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                // Close the file stream
                if (fileStream != null)
                {
                    fileStream.Close();
                }

                // Close the message
                if (msgStream.CanSeek)
                {
                    msgStream.Position = 0;
                }
            }
        }

        #endregion
    }
}

Geen opmerkingen:

Een reactie posten