Serializer Implementation

In this section, we will implement the serializer design from the preceding section using C++ and ATL. Unlike the functoid implementation earlier, there is not a method for implementing the serializer using Visual Basic. The COM interface uses types, such as IStream, not accessible from Visual Basic.

The implementation comprises the following items:

  • BizTalk envelope, port, and channel configuration

  • A COM coclass implementing IBizTalkSerializerComponent

The COM coclass provides the following:

  • Serialization— Conversion of XML to the output format

  • Group separation— Identify grouping boundaries

  • Sizing— Identify sizes of documents, groups, and the interchange

For a test harness, we will use the same HTML application used to test the parser.

Configuring an Envelope, Port, and Channel for a Custom Serializer

Figure 18.18 shows the channel we will configure. Most of the configuration is in place. We will focus on steps six and seven in the following discussion.

Figure 18.18. Channel configuration for our custom serializer.


To update the configuration from our parser, start the BizTalk Messaging Manager.

Note

If you skipped ahead, briefly return to the section “Configuring a Channel for a Custom Parser.” Follow the configuration steps and then return here.


Create an envelope for the custom data format our serializer will produce as follows. Select File, New, Envelope from the menu and name it envelopeBroker2_ch18. Choose CUSTOM in the Envelope Format combo box because our format is binary. Leave the Envelope Specification check box unchecked. Click OK.

Next, we will modify our port, portBroker2_ch18, and channel, channelBroker_ch18. The port needs a reference to our new envelope, and the channel needs a reference to our new serializer component.

Trying to change either one results in an error message simply because of mutual dependencies. To break the dependency, delete the channel; we will re-create it.

After deleting the channel, locate the port portBroker2_ch18. Double-click it to edit. Click Next twice to get to the Envelope Information page. In the Envelope Information combo box, select the envelope just created, envelopeBroker2_ch18. Click Finish.

Now, we will re-create the channel. Right-click the port and select New Channel, From an Organization from the menu. As before, name the new channel channelBroker_ch18 with comments This channel accepts an inbound batch of documents from a broker organization via an HTTP Receive Function. Click Next. Select the Open Source radio button. Click Next. Browse for the inbound document definition definitionBrokerItem_ch18. Click Next. Browse for the same output document definition definitionBrokerItem_ch18. Click Next. Click Next again. On the Advanced Configuration page, click the Advanced button. On the Envelope tab, examine the combo box Serializer Component. When we build our serializer component, it will show up here, as shown in Figure 18.19. For now, click OK without selecting a serializer. Click Finish.

Figure 18.19. Serializers registered with BizTalk.


Configuring a Test Harness for the Custom Serializer

For a test harness, we will use the same HTML application and an HTTP receive function used for testing the parser.

Note

If you skipped ahead, briefly return to the section “Configuring a Test Harness for the Custom Parser.” Follow the configuration steps and then return here.


If you are using a file port rather than an e-mail port for the test harness, we need to specify an output file that ends in .bmp instead of .xml. To do so, launch the BizTalk Messaging Manager. Locate the port portBroker2_ch18, and double-click it to edit. Click Next. Click the Browse button for the Primary Transport. Update the filename and click OK. Click Finish.

To confirm that the test harness is configured properly, launch http://localhost/BizTalkUnleashed/batchsubmit_ch18.asp as we did for the parser. Click the View Batch button. Click the Add Sample button. Click the Submit Batch button to post over HTTP the entire batch to the HTTP receive function.

No serializer is installed yet, so the submission should fail inside BizTalk after the parsing step. To confirm, launch BizTalk Server Administration and open the paths Event Viewer (local)Application and Microsoft BizTalk Server 2002BizTalk Server GroupQueuesSuspended Queue. The document state shows as Serializing, and the error description shows as Serializing Failure. To see the document data, right-click the item in the Suspended Queue and select View Document. Figure 18.20 shows the results.

Figure 18.20. Serializing failure is expected at this point.


In the event log, there is an error entry. Double-click it to see a description that the server could not finish processing our message port, portBroker2_ch18.

ATL Shell Implementation

The first step in the COM implementation of the serializer is making an ATL shell. The shell will contain a fully compilable C++ project with a sample implementation for IBizTalkSerializerComponent. We assume the latest release of Visual Studio 6, which is SP5.

Note

A custom ATL Object Wizard should be available at this point. If you skipped ahead, then briefly return to the section “ATL Shell Implementation” for custom functoids. Follow the instructions for installing the ATL Object Wizards and return here.


Create an empty ATL project in Visual Studio 6 as follows. Select File, New from the menu and select ATL COM App Wizard from the Projects tab. Enter FormPostBatchSerializer in the Project Name edit box and fill in the Location edit box to some suitable directory. Click OK. Click Finish on the next screen and then click OK on the next.

Next, run the ATL Object Wizard as follows. Select Insert, New ATL Object from the menu. Select BizTalk Unleashed in the Category list box and Custom Serializer in the Objects list box, as shown in Figure 18.21. Click Next. Enter FormPostSerializer in the Short Name list box. Change the ProgID to BizTalkUnleashed.FormPostBatchSerializer. Click OK.

Figure 18.21. ATL Object Wizard for a custom serializer.


Note

If there are no choices for BizTalk Unleashed and Custom Serializer, then the install of the ATL Object Wizards did not complete. Please retry and confirm each step.


Table 18.12 shows the actions the ATL Object Wizard took on our project. Note that the sample files with this chapter match the names of the files in this table. The description of each file varies somewhat because the samples files are the complete implementation rather than the starter files generated by the wizard.

Table 18.12. ATL Object Wizard Actions for Serializers
Project FileWizard Action
FormPostSerializer.hCreated this file for a standard ATL declaration of a coclass, CFormPostSerializer. Adds COM category map, BizTalk SDK includes, and IBizTalkSerializerComponent.
FormPostSerializer.cppCreated this file for a standard implementation of the coclass CFormPostSerializer. Adds interface stubs with ATLTRACE statements including custom tracers.
FormPostBatchSerializer.idlAdded to this project IDL file. From BizTalk SDK file BTSSerializerComps.idl, uses IBizTalkSerializerComponent.
FormPostSerializer.rgsCreated this file for standard COM registration. Resource.h
FormPostBatchSerializer.rcUpdated for the Registry resource.

The ATL Object Wizard forces ThreadingModel=Both regardless of the threading model selection in the wizard. Although this setting is not documented in BizTalk, the stock serializers shipped by BizTalk all have ThreadingModel=Both.

A shell implementation in ATL for the custom serializer is complete. We will examine and extend the implementation later in the section “Adding Custom Functionality.”

Before compiling, confirm that the include directory for the BizTalk SDK is available to your project. Select Tools, Options from the menu. On the Directories tab, select Include Files in the Show Directories For list box. Add the BizTalk SDK include path, typically found at C:Program FilesMicrosoft BizTalk ServerSDKInclude. Click OK.

Also, confirm that C++ Exception Handling is enabled for your project. Select Project, Settings from the menu. Select All Configurations in the Settings For list box. Select C++ Language in the Category combo box on the C++ tab. Check Enable Exception Handling and click OK.

Build the project by selecting Build, Rebuild All from the menu. The build should complete with no errors, and the COM registration of the new serializer should succeed.

To see that BizTalk recognizes the new serializer, start BizTalk Messaging Manager. Locate the channel named channelBroker_ch18. Double-click to edit it. Click the Next button five times to get to the Advanced Configuration page. Click the Advanced button. On the Envelope tab, open the combo box Serializer Component to see whether our component is there. Figure 18.22 shows an expected list. Go ahead and select our component and click OK. Click Finish.

Figure 18.22. BizTalk recognizes our new serializer.


Tip

If the serializer did not appear in the combo box, then double-check that the registration succeeded during the build. BizTalk locates our component by checking the registration of all COM components. It looks for components with a specific COM category. Our ATL implementation provides that category using ATL's category map in FormPostSerializer.h.


Adding Custom Functionality

We are ready to add the specifics of the form post serializer to the implementation. The completed serializer is in the sample files. Unlike the custom functoid section, we will not incrementally change the ATL Object Wizard output. Rather, we will examine each function in the implementation in detail.

Interface Overview

The IBizTalkSerializerComponent interface has five functions, as shown in Table 18.13.

Table 18.13. Functions in IBizTalkSerializerComponent
Interchange LevelInit()Initialize the serializer.
 GetInterchangeInfo()Get tracking and grouping information.
Group LevelGetGroupInfo()Get information for tracking a group.
Document LevelAddDocument()Serialize one document.
 GetDocInfo()Get information for tracking a document.

BizTalk calls Init() to pass an output stream to the parser and to indicate how many documents are to be serialized in the interchange. BizTalk then calls AddDocument() successively, once for each document in the interchange. At this point, no grouping information has been specified. BizTalk next calls GetInterchangeInfo() to get a tracking ID for the interchange and to get the number of groups the serializer will impose on the set of documents already serialized. If zero groups are specified, then BizTalk calls GetDocInfo() for each document already serialized to get offset and size within the output stream. Alternatively, if groups are specified, then BizTalk calls GetGroupInfo() to get offset and sizing information for the first group. GetGroupInfo() also outputs how many documents are in the first group. BizTalk then calls GetDocInfo() repeatedly for the number of documents specified in the group. BizTalk repeats this GetGroupInfo()/GetDocInfo() usage for the remaining groups.

BizTalk, however, only uses a subset of the serializer interface in the BizTalk 2002 release. The Init() function always specifies one for the number of documents in the interchange. The GetInterchangeInfo() may only return zero or one for the number of groups. With a maximum of one document and one group in an interchange, grouping is not meaningful. The grouping API is a placeholder for a future release.

IBizTalkSerializerComponent::Init()

Init() receives the output stream that the serializer will use. It also receives the number of documents it will serialize into that stream. For the BizTalk 2002 release, this number will always be one.

Listing 18.15 is a concise form of our sample serializer's implementation. The sample source code with this chapter has a full version with documentation, tracking, and error checking.

Listing 18.15. Concise Implementation of Init() (FormPostSerializer.cpp)
HRESULT CFormPostSerializer::Init([in] BSTR bstrSrcQual, [in] BSTR bstrSrcID, [in] BSTR
 bstrDestQual, [in] BSTR bstrDestID, [in] long nEnvID, [in] IDictionary * pdictDelims, [in]
 IStream * pstmOut, [in] long nDocs, [in] long nPortID)
{
   this->m_spstmOutput = pstmOut;
   return S_OK;
}
/*  This function addrefs the given IStream, retains a copy in class member
    m_spstmOutput., and returns S_OK.  None of the other input parameters are needed.
*/

The parameters bstrSrcQual, bstrSrcID, bstrDestQual, and bstrDestID give the qualifiers and identifiers for the source and target organizations. Use these parameters for organization-specific handling.

Parameter nPortID identifies the port to which the channel using the custom serializer is connected. This port has an envelope whose identifier is given by nEnvID. The envelope for a port is set using the BizTalk Messaging Manager. On the same property page for setting the envelope is a property for an optional set of delimiters to use during serialization. These delimiters are passed into this function using the dictionary parameter pdictDelims.

The parameter pstmOut is the stream into which the serializer is to write all output. This IStream interface is write-only, and only has members Write() and Stat() implemented. The custom serializer must retain a reference-counted copy of the given IStream if it returns S_OK. In the sample code in Listing 18.15, we retain it in a data member of type CComPtr<IStream> whose assignment operator does an implicit addref. Subsequent method calls on this interface will need access to the stream.

The parameter nDocs is always one in BizTalk 2002. In a future release, it will specify the number of documents in the interchange.

IBizTalkSerializerComponent::AddDocument()

AddDocument() formats one document in the output format and writes it to the stream that was passed into the Init() function.

Listing 18.16 is a concise form of our sample serializer's implementation. The sample source code with this chapter has a full version with documentation, tracking, and error checking.

Listing 18.16. Concise Implementation of AddDocument() (FormPostSerializer.cpp)
HRESULT CFormPostSerializer::AddDocument([in] long nDocHandle, [in] IDictionary * pdict,
 [in] BSTR bstrTrackID, [in] long nChannelID)
{
   CComVariant vaKey(L"working_data"), vaDoc;
   pdict->get_Value(vaKey.bstrVal, &vaDoc);  vaDoc.ChangeType(VT_BSTR);
   CComPtr<XMLDOM::IXMLDOMDocument2> spxml;  VARIANT_BOOL bLoaded;
   spxml.CoCreateInstance(__uuidof(XMLDOM::DOMDocument30));
   spxml->loadXML(vaDoc.bstrVal, &bLoaded);
   CComVariant vaB = xmllookup(spxml, L"//broker", VT_BSTR, L"???");
   CComVariant vaS = xmllookup(spxml, L"//stock", VT_BSTR, L"???");
   CComVariant vaQ = xmllookup(spxml, L"//quantity", VT_I4, 0L);
   CComVariant vaP = xmllookup(spxml, L"//min-price", VT_R4, 0.0f);
   time_t tm = time(0); TCHAR sz[1024];
   _stprintf(sz, _T("Stock Trade Receipt

     %ls
")
                 _T("Broker:  %ls
Stock:  %ls

%d share%s at $%0.2f"),
             tasctime(localtime(&tm)), vaB.bstrVal, vaS.bstrVal, vaQ.lVal,
             (vaQ.lVal == 1) ? _T("") : _T("s"), vaP.fltVal);
   CComPtr<IPicture> sppict = makeReceiptBitmap(sz);
   sppict->SaveAsFile(m_spstmOutput, TRUE, &m_nDocSize);
   m_nDocHandle = nDocHandle;
   return S_OK;
}
/*  This function gets the XML document as text from the transport dictionary
    under the key "working_data".  Next, it instantiates a new XML DOM and
    loads the XML text.  It then extracts the broker, stock, quantity, and
    price fields using the helper function xmllookup().  That function takes
    an XML DOM, an XPath expression to a value, a variant type for the return
    value, and a default value.  The fields are formatted into a text string
    that is then rendered onto a bitmap using the function
    makeReceiptBitmap().  The bitmap is contained in an IPicture which is used
    to save to the output stream, m_spstmOutput.  The number of bytes written
    and the document handle are recorded in class members m_nDocSize and
    m_nDocHandle, respectively, for future use by GetDocInfo().  This function
    returns S_OK.
*/

Input parameter bstrTrackID provides the identifier used in the tracking database for this document. Input parameter nChannelID is the identifier of the channel using this serializer. Our sample serializer does not need either of these parameters.

Input parameter pdict is the transport dictionary that contains the XML document to be serialized. The document appears under the dictionary key working_data. Remember that dictionary keys are not case sensitive.

Our sample parser extracts the XML document and renders an image in the bitmap format shown earlier in the section Format of Data Output by the Serializer.

Input parameter nDocHandle is an integer handle used by BizTalk to uniquely identify this document. This handle is valid only for the life of this serializer instance.

GetDocInfo() requires us to map each document handle originally passed into AddDocument() to two values: the size and offset of the document in the output stream. We use a simplified implementation because there will only be one document. In our implementation, the offset always will be zero. We record just a single handle and size in data members m_nDocHandle and m_nDocSize, respectively.

IBizTalkSerializerComponent::GetInterchangeInfo()

GetInterchangeInfo() returns information about the entire interchange after all documents have been serialized.

Listing 18.17 is a concise form of our sample serializer's implementation. The sample source code with this chapter has a full version with documentation, tracking, and error checking.

Listing 18.17. Concise Implementation of GetInterchangeInfo() (FormPostSerializer.cpp)
HRESULT CFormPostSerializer::GetInterchangeInfo([out] BSTR * pbstrID, [out] long *
 pnNumGroups)
{
   GUID guid; wchar_t wszGuid[128];
   CoCreateGuid(&guid);  StringFromGuid2(guid, wszGuid, sizeof wszGuid);
   *pbstrID = CComBSTR(wszGuid).Detach();
   *pnNumGroups = 0;
   return S_OK;
}
/*  This function sets output parameter *pbstrID to a new guid, sets the
    number of groups for the interchange to zero, and returns S_OK.
*/

Set output parameter *pbstrID to a new ID to be used for the interchange in the tracking database. Our sample serializer allocates a new GUID and converts it to a string.

Set output parameter *pnNumGroups to the number of groups in the interchange. For BizTalk 2002, this value must be zero or one. Our sample serializer sets it to zero.

IBizTalkSerializerComponent::GetGroupInfo()

GetGroupInfo() returns information about one group for the tracking database.

Listing 18.18 is a concise form of our sample serializer's implementation. The sample source code with this chapter has a full version with documentation, tracking, and error checking.

Listing 18.18. Concise Implementation of GetGroupInfo() (FormPostSerializer.cpp)
HRESULT CFormPostSerializer::GetGroupInfo([out] long * pnNumDocs, [out] LARGE_INTEGER *
 pnOffset, [out] long * pnGroupLen)
{
   return E_UNEXPECTED;
}
/*  This function returns E_UNEXPECTED because groups are not supported and
    this function should not be called.
*/

Set output parameter *pnNumDocs to the number of documents in the group. This value must be one because only one document per interchange is supported in BizTalk 2002.

Set output parameter *pnOffset to the offset of the start of the group in the output stream. Given that only one group is allowed, this offset must be zero.

Set output parameter *pnGroupLen to the total number of bytes in all documents in the group. Given that only one document is allowed, this value is also the size of the one document.

Our sample serializer does not support groups, so it returns E_UNEXPECTED.

IBizTalkSerializerComponent::GetDocInfo()

GetDocInfo() returns information about one document for the tracking database.

Listing 18.19 is a concise form of our sample serializer's implementation. The sample source code with this chapter has a full version with documentation, tracking, and error checking.

Listing 18.19. Concise Implementation of GetDocInfo() (FormPostSerializer.cpp)
HRESULT CFormPostSerializer::GetDocInfo([out] long * pnHandle, [out] BOOL *
 pbSizeFromXMLDoc, [out] LARGE_INTEGER * pnOffset, [out] long * pnLen)
{
   *pnHandle = m_nDocHandle;
   *pnLen = m_nDocSize;
   *pnOffset->QuadPart = 0;
   *pbSizeFromXMLDoc = FALSE;
   return S_OK;
}
/*  This function sets output parameters to the handle and size to the values
    recorded by AddDocument().  The offset is set to 0 because there is only
    one document.  The boolean output parameter is set to false to flag that
    size and offset are set by this function.  This function returns S_OK.
*/

Set output parameter *pnHandle to the handle the next document in the current group. Given that only one document is allowed in the interchange, the handle is the value passed to the one call to AddDocument(). Our sample serializer uses the value recorded in data member m_nDocHandle.

Set output parameter *pbSizeFromXMLDoc=TRUE and BizTalk will calculate the size and offset of the document in the output stream. In this case, the last two parameters are not used.

To calculate the size and offset, set *pbSizeFromXMLDoc=FALSE. Set *pnOffset to the offset within the stream. This value must be zero because only one document is allowed. Set *pnLen to the total number of bytes the document occupies in the stream.

Our sample serializer sets *pbSizeFromXMLDoc=FALSE. It sets the offset to zero and uses data member m_nDocSize for *pnLen. Method AddDocument() set the value of m_nDocSize earlier.

Debugging the Serializer

Debugging the serializer follows the same steps for debugging the parser.

Summarizing these steps, we will use a file receive function to simplify setting breakpoints. The Visual C++ project sets the debugging process to be MSCIS.EXE. The BizTalk Messaging service, MSCIS.EXE, needs to be stopped and set to manual start without any restart options. Launch MSCIS.EXE in the debugger and drop a Unicode text file into the folder monitored by the file receive function. Watch out for automatic disabling of the file receive function if errors occur. MSCIS.EXE and DLLHOST.EXE will hold onto our DLL preventing a rebuild. Shutting down these processes is sometimes necessary. Remember that documents will sometimes automatically resubmit themselves from the Work Queue, which is managed in BizTalk Server Administration. After debugging, remember to reinstate MSCIS.EXE settings modified during debugging such as auto-start.

In addition, there are specific debugging considerations for the serializer. Consider submitting an exchange with a single document in it during early testing. BizTalk will run the serializer simultaneously on each parsed document. The result is a difficult debugging walk-through for the code. BizTalk will instantiate the serializer component once for each document and use the instances simultaneously. Note that each serializer instance will handle exactly one document, so race conditions within a component should not occur. Of course, a race condition can occur if you rely on global state. Be careful about functions in the C runtime library that maintain state between calls such as strtok(). We did not face this problem during parsing because parsing a single stream is inherently a singular task.

Note

Our custom serialization component should already be configured for use by the channel. If you skipped ahead, briefly return to Figure 18.22. Follow the instructions for selecting our serializer and return here.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset