Saturday, September 06, 2008

Multiple Flat File Schemas Disassembler

The problem with Flat File Disassembler is that doesn't allow multiple Document Schemas as XML Disassembler does.

In the BizTalk SDK, there is a sample pipeline component that allows this feature (see the project inside \Microsoft BizTalk Server 2006\SDK\Samples\Pipelines\SchemaResolverComponent\SchemaResolverFlatFileDasm), but this component only recognizes different documents if them have a Tag Identifier in the first two characters.

My problem was a bit different. The difference between documents is in a concrete position:

DOCUMENT DATA MESSAGE TYPE 65 DOCUMENT DETAIL

The info about wich is the flat file schema is inside the message (in previous sample, was 65 the identifier).

I have modified the SDK sample Pipeline Component to select the correct schema with the info inside the different messages.

First of all, I added two new properties: StartPosition and ReadLength. StartPosition indicates where the identifier characters starts. ReadLength is the identifier's length.
private int startPosition = 0;
private int readLength = 0;

public int StartPosition
{
get
{
return startPosition;
}
set
{
startPosition = value;
}
}

public int ReadLength
{
get
{
return readLength;
}
set
{
readLength = value;
}
}
Second step is to implement the IPersistPropertyBag, to save and load this properties:
public void Load(IPropertyBag propertyBag, int errorLog)
{
object property = null;

propertyBag.Read("StartPosition", out property, errorLog);
if (property != null)
StartPosition = (int)property;

propertyBag.Read("ReadLength", out property, errorLog);
if (property != null)
ReadLength = (int)property;
}

public void Save(IPropertyBag propertyBag, bool clearDirty, bool saveAllProperties)
{
object ptrVar = StartPosition;
propertyBag.Write("StartPosition", ref ptrVar);
ptrVar = ReadLength;
propertyBag.Write("ReadLength", ref ptrVar);
}
Third and last step is modify the Probe method, that reads the message stream, at the StartPosition position ReadLength characters:
public bool Probe(IPipelineContext pContext, IBaseMessage pInMsg)
{
if (null == pContext)
throw new ArgumentNullException("pContext");

if (null == pInMsg)
throw new ArgumentNullException("pInMsg");

if (null == pInMsg.BodyPart || null == pInMsg.BodyPart.GetOriginalDataStream())
return false;

ReadOnlySeekableStream stream = new ReadOnlySeekableStream(pInMsg.BodyPart.GetOriginalDataStream());
Stream sourceStream = pInMsg.BodyPart.GetOriginalDataStream();

if (!sourceStream.CanSeek)
{
ReadOnlySeekableStream seekableStream = new ReadOnlySeekableStream(sourceStream);

pInMsg.BodyPart.Data = seekableStream;

sourceStream = pInMsg.BodyPart.Data;
}

long position = sourceStream.Position;

char[] identifier = new char[ReadLength];
try
{
StreamReader reader = new StreamReader(sourceStream);
reader.BaseStream.Position = StartPosition;
if (reader.Read(identifier, 0, identifier.Length) < identifier.Length)
return false;

}
finally
{
sourceStream.Position = position;
}

string messageType = GetMessageType(new string(identifier));

if (null == messageType)
return false;

IDocumentSpec documentSpec = pContext.GetDocumentSpecByType(messageType);

pInMsg.Context.Write(DOCUMENT_SPEC_NAME_PROPERTY_NAME, XML_NORM_NAMESPACE_URI, documentSpec.DocSpecStrongName);

return disassembler.Probe(pContext, pInMsg);
}
At last, GetMessageType method in SDK sample, hard-coded the schemas DocumentSpec. We can modify this method to retrieve this information from a config file or SSO as config store.

1 comment:

Anonymous said...

Thank you, that was extremely valuable and interesting...I will be back again to read more on this topic.