Ce post décrit une méthode bien pratique pour s’affranchir des problèmes liés aux changements de version des iDocs envoyés par un système SAP.
Point sur la génération des schémas
L’adapter Pack 2.0 pour BizTalk offre la possibilité de générer les schémas d’iDoc de SAP. Trois propriétés rentrent en compte lors de la génération de ces schémas, en plus du type d’iDoc choisi :
· Le numéro de version
· La release
· L’extension
Rappelons que BizTalk se base sur deux éléments pour déterminer le type d’un message : le nœud racine d’une part, et le namespace d’autre part. Lors de la génération des schémas par l’Adapter Pack, ce namespace est créé en respectant cette convention :
http://Microsoft.LobServices.Sap/2007/03/Idoc/{Version}/{TypeIdoc}/{Extension}/{Release}/Receive
Par défaut, SAP génère les iDocs en fonction de la version de release courante (active). Néanmoins, il est possible d’envoyer une version d’idoc spécifique. Si, lors de la génération des schémas, l’un de ces paramètres n’a pas été choisi conformément à ce que le système SAP enverra réellement, une erreur sera remontée par BizTalk à la réception de l’iDoc, par exemple :
There was a failure executing the receive pipeline: "Microsoft.BizTalk.DefaultPipelines.XMLReceive, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "XML disassembler" Receive Port: "WcfReceivePort_SAPBinding_IdocZHR_KPV3R710_Custom" URI: "sap://CLIENT=XXX;LANG=EN;@a/SAPSERVER/XX?ListenerGwServ=SAPGWXX&ListenerGwHost=SAPSEVER& ListenerProgramId=XXXXXX&RfcSdkTrace=False&AbapDebug=False" Reason: Finding the document specification by message type "http://Microsoft.LobServices.Sap/2007/03/Idoc/3/ZHR_KP//700/Receive#Receive" failed. Verify the schema deployed properly.
Une solution : le changement dynamique du namespace
La solution retenue dans ce post est de changer à la volée le namespace de l’iDoc entrant de manière à le faire correspondre avec celui des schémas générés dans Visual Studio. Ceci passe par la création d’un nouveau pipeline component, et la création d’un custom pipeline embarquant ce nouveau composant.
Création de la structure du Custom Pipeline Component
L’utilisation du BizTalk Server Pipeline Component Wizard simplifiera grandement cette étape. Celui-ci est téléchargeable sur CodePlex (attention à la version : 2.1 pour BTS 2009, et 2.2 pour BTS 2010) à l’adresse suivante : http://btsplcw.codeplex.com/
Une fois cet assistant installé, la création du pipeline component passe par la création d’un nouveau projet. Dans la fenêtre de création d’un projet, sélectionner le type de projets BizTalk Projects, puis sélectionner le template BizTalk Server Pipeline Component. Les étapes de l’assistant sont les suivantes :
1. Renseigner le nom de la classe générée, le namespace de la classe, le type du pipeline (dans le cas qui nous intéresse : Receive) et le type de composant (l’étage le plus adapté ici est Decoder).
2. Renseigner les infos générales sur le composant lui-même, à savoir son nom, sa version, sa description et l’icône qui apparaitra dans la barre des outils de Visual Studio.
3. Créer les paramètres du futur composant :
o IDocVersion (int)
o IDocRelease (int)
o IDocExtension (string)
Une fois l’assistant terminé, un nouveau projet est bien créé et comporte un fichier de classe embarquant toute la logique du composant et un fichier de ressources contenant les propriétés d’affichage du composant (nom, description et version) et son icône.
Modification des namespaces
Il faut maintenant modifier le code du composant pour y effectuer le changement dynamique du namespace des idocs entrants. Plus précisément, il va s’agir de modifier la méthode Execute (qui est un membre de l’interface IComponent).
Il s’agit dans un premier temps de récupérer le flux XML depuis le corps du message et le stocker dans un XmlDocument pour en faciliter la manipulation :
IBaseMessagePart bodyPart = inmsg.BodyPart;
XmlDocument originalDoc = new XmlDocument();
originalDoc.Load(bodyPart.GetOriginalDataStream());
On change ensuite les namespaces par simple manipulation et remplacement de chaînes de caractères :
string oldXmlns = originalDoc.DocumentElement.Attributes["xmlns"].Value;
string xmlns = originalDoc.DocumentElement.Attributes["xmlns"].Value.Replace(@"http://Microsoft.LobServices.Sap/2007/03/Idoc/", "").Replace(@"/Receive", "");
List<string> xmlnsElts = new List<string>(xmlns.Split(((string)@"/").ToCharArray()));
string version = xmlnsElts[0];
string idoc = xmlnsElts[1];
string extension = xmlnsElts[2];
string release = xmlnsElts[3];
string newXmlns = String.Format(@"http://Microsoft.LobServices.Sap/2007/03/Idoc/{0}/{1}/{2}/{3}/Receive",
this.IDocVersion,
idoc,
this.IDocExtension,
this.IDocRelease);
string oldTypeXmlns = String.Format(@"http://Microsoft.LobServices.Sap/2007/03/Types/Idoc/{0}/{1}/{2}/{3}",
version,
idoc,
extension,
release);
string newTypeXmlns = String.Format(@"http://Microsoft.LobServices.Sap/2007/03/Types/Idoc/{0}/{1}/{2}/{3}",
this.IDocVersion,
idoc,
this.IDocExtension,
this.IDocRelease);
StringBuilder outputMsgText = new StringBuilder();
outputMsgText.Append(String.Format("<{0} xmlns=\"{1}\">",
originalDoc.DocumentElement.Name,
newXmlns));
outputMsgText.Append(originalDoc.DocumentElement.InnerXml
.Replace(oldXmlns, newXmlns)
.Replace(oldTypeXmlns, newTypeXmlns));
outputMsgText.Append(String.Format("</{0}>",
originalDoc.DocumentElement.Name));
On peut alors remplacer le corps du message reçu :
byte[] outBytes = System.Text.Encoding.UTF8.GetBytes(outputMsgText.ToString());
MemoryStream memStream = new MemoryStream();
memStream.Write(outBytes, 0, outBytes.Length);
memStream.Position = 0;
bodyPart.Data = memStream;
pc.ResourceTracker.AddResource(memStream);
Il ne reste alors qu’à promouvoir la propriété BTS.MessageType afin de permettre au message d’être correctement routé :
string msgType = newXmlns + "#" + originalDoc.DocumentElement.Name;
inmsg.Context.Promote("MessageType",
@"http://schemas.microsoft.com/BizTalk/2003/system-properties",
msgType);
return inmsg;
Mise en oeuvre
Une fois le composant terminé, il faut ajouter la DLL du projet correspondant dans le répertoire Pipeline Components situé dans le répertoire d’installation de BizTalk, puis l’ajouter à la Toolbox de Visual Studio.
Il est alors possible de créer un pipeline et d’y insérer le nouveau composant à l’étage Decompose. Les propriétés IDocVersion, IDocRelease et IDocExtension pourront être fixées au moment de la création du pipeline ou directement par configuration dans la console d’administration BizTalk.
Solution alternative : utilisation d’un étage de désassemblage de fichier plat
Une autre solution de contournement impliquant la création de custom pipelines et de Flat File Disassembler stages est décrite intégralement sur ce blog : Kent Weare @BlogSpot. Mais elle me parait plus longue à mettre en œuvre et surtout beaucoup moins générique.