Functoid Implementation

In this section, we will implement the Selector functoid design from the preceding section using C++ and ATL. For alternative implementation technologies, check the BizTalk product samples. They provide functoids in Visual Basic and C#.

The implementation in this section comprises the following items:

  • A script implementing the Selector() functionality

  • A COM coclass implementing IFunctoid

It is reasonable to ask why there is a mixture of COM and script. Script is not sufficient. BizTalk Mapper needs additional metadata for how to incorporate the functoid onto the mapper's diagram surface. BizTalk Mapper also uses COM categories to catalog available functoids.

We cannot eliminate the script entirely in favor of a COM object either. BizTalk Mapper pastes the script into an XSLT transformation. Although XSLT transformations could access COM objects, BizTalk Mapper favors scripting.

Script Implementation

BizTalk Mapper supports VBScript and JScript. Listing 18.1 has the JScript implementation of the Selector functoid. The ATL implementation will contain a function that returns this script as a string later in the chapter.

Listing 18.1. JScript Implementation for Selector
function Selector ( j, val1, val2 )
{
    try
    {
        // Normalize the various forms of the input to an integer
        switch (j.constructor) // let's check the type
        {
           case Number:  // make sure any number is integer
                j = Math.floor(j); // e.g., use 1 if have 1.1
                break;
           case Boolean: // use 1 for true and 2 for false
                j = j ? 1 : 2;
                break;
           case String: // string could hold bool or number
                switch (j.toUpperCase())
                {
                     case "TRUE": j = 1; break;
                     case "FALSE": j = 2; break;
                     default: j = Math.floor(parseInt(j)); break;
                }
                break;
           default:
                j = 0; // flag error with invalid index
                break;
        }

        // return the selected value, if any
    return eval('val' + j);
    }
    catch ( x )
    {
    }
    return ""; // error return
}

The implementation starts by checking the type of the first argument. The task is to normalize any noninteger into an integer index. Per the design specification earlier, boolean values use index 1 for true and index 2 for false. In the case of a string, we look for a boolean or number in the string.

This implementation assumes three parameters. In general, there can be many parameters. Later, we will implement a method on IFunctoid to return the script given a specific number of parameters. The script modification is only in the function's parameter list. Simple inspection will reveal how simple this extension is.

ATL Shell Implementation

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

First, install a custom ATL Object Wizard in two simple steps. Locate the folder ATL Object Wizards in the sample code accompanying this book. In that folder is a .reg file and a folder named BizTalk Unleashed. Merge the Registry settings file by right-clicking it and selecting Merge or by double-clicking the file. Next, copy the entire BizTalk Unleashed folder to the Visual C++ template folder, typically at C:Program FilesMicrosoft Visual StudioCommonMSDev98TemplateATL. The result should place a BizTalk Unleashed folder directly under the CommonMSDDev98TemplateATL folder. This step actually installs three ATL Object Wizards. The other two are needed later in this chapter.

The next step is to create an empty ATL project. Launch Visual Studio 6. Select File, New from the menu and select ATL COM App Wizard from the Projects tab. Enter SelectorFunctoid 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 Functoid in the Objects list box as shown in Figure 18.5. Click Next. Enter Selector in the Short Name list box. Change the ProgID to BizTalkUnleashed.SelectorFunctoid. Click OK.

Figure 18.5. ATL Object Wizard for a custom functoid


Tip

If there are no choices for BizTalk Unleashed and Custom Functoid, then the install of the ATL Object Wizard did not complete. Retry and confirm each step.


The custom functoid implementation in ATL is complete. We will examine and extend the implementation 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 functoid should succeed.

To test, launch the BizTalk Mapper. Open any map. To use a stock map, select File, Retrieve from WebDAV and select any of the standard ones Microsoft provides. Show the Functoid Palette by selecting View, Functoid Palette from the menu. On the tool window for the palette, navigate to the last tab, labeled Advanced. There should be two blank functoids.

Understanding the ATL Shell Implementation

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

Table 18.3. ATL Object Wizard Actions for Functoids
Project FileWizard Action
Selector.hCreated this file for a standard ATL declaration of a coclass, CSelector. Adds COM category map, IFunctoidImpl.h, IFunctoidDispatchImpl, DECLARE_FUNCTOID_MAP(), and two functions with the signature format BSTR f(long).
Selector.cppCreated this file for a standard implementation of the coclass CSelector. Adds the BEGIN_FUNCTOID_MAP macro and two functions with the signature format BSTR f(long).
SelectorFunctoid.idlAdded to this project IDL file. Uses IFunctoid from the import of the BizTalk SDK file functoid.idl.
Selector.rgsCreated this file for standard COM registration.
IFunctoidImpl.hHelper code provided by the wizard. Contains functoid map macros and the ATL-compliant class IFunctoidDispatchImpl.
Functoid.bmp16×16 image used for the Functoid Palette in BizTalk Mapper.
FunctoidBlanks.bmpVector of 16×16 images. Each one matches the background color and 3D shading of the BizTalk Mapper built-in functoids. Helps create images matching the Mapper's color scheme.
Resource.h SelectorFunctoid.rcUpdated for the two bitmaps and a Registry resource.

We will explore the implementation in full detail as we add functionality specific to the Selector example designed earlier. For now, we will build an intuition by examining elements that directly correspond to the BizTalk Mapper GUI.

The wizard uses a map-based implementation commonly found in ATL. The idea is to use metadata to drive an interface implementation. To define the metadata, macros give a human readable format. These macros usually populate internal data structures of the class. For example, the functoid map in Listing 18.2 shows the metadata for two custom functoids.

Listing 18.2. Functoid Map (Selector.cpp)
BEGIN_FUNCTOID_MAP(CSelector, _VERSION, FUNCTOID_FIRST_USER_FUNCID)
   BEGIN_FUNCTOID(L"Selector #1", FUNC_CATEGORY_UNKNOWN)
        FUNCTOID_TOOLTIP(L"Custom functoid with exactly 3 input params.")
        FUNCTOID_BITMAP(IDB_FUNCTOID_SELECTOR)
        FUNCTOID_OUT(CONNECT_TYPE_ALL)
        FUNCTOID_IN(CONNECT_TYPE_ALL_EXCEPT_RECORD)
        FUNCTOID_IN(CONNECT_TYPE_ALL_EXCEPT_RECORD)
        FUNCTOID_IN(CONNECT_TYPE_ALL_EXCEPT_RECORD)
        FUNCTOID_IMPL(SCRIPT_CATEGORY_JSCRIPT, GetFunctoidImpl1)
   END_FUNCTOID
   BEGIN_FUNCTOID(L"Selector #2", FUNC_CATEGORY_UNKNOWN)
        FUNCTOID_TOOLTIP(L"Custom functoid with at least 1 input param.")
        FUNCTOID_BITMAP(IDB_FUNCTOID_SELECTOR)
        FUNCTOID_OUT(CONNECT_TYPE_ALL)
        FUNCTOID_IN(CONNECT_TYPE_ALL_EXCEPT_RECORD)
        FUNCTOID_IN_OPTIONAL(CONNECT_TYPE_ALL_EXCEPT_RECORD)
        FUNCTOID_IMPL(SCRIPT_CATEGORY_VBSCRIPT, GetFunctoidImpl2)
   END_FUNCTOID
END_FUNCTOID_MAP

To visualize how some of the metadata gets used, return to BizTalk Mapper. On the Advanced tab of the Functoid Palette are two custom functoids with blank icons, as shown in Figure 18.6. Each one corresponds to a BEGIN_FUNCTOID/END_FUNCTOID pair in the map. The exact position of the blank icons might vary from Figure 18.6.

Figure 18.6. Two new functoids on the Advanced tab.


Hover the mouse above one of the blank icons on the palette until a ToolTip appears as in Figure 18.7. It has the form “name: description”. Notice that the names and descriptions in BizTalk Mapper appear in the BEGIN_FUNCTOID and FUNCTOID_TOOLTIP macros shown in Listing 18.2.

Figure 18.7. Functoid ToolTip.


Both functoids have the same blank image. The FUNCTOID_BITMAP macros shown previously also have the same IDB_FUNCTOID_SELECTOR resource ID. Check the project resources to find that the bitmap matches the image in BizTalk Mapper.

In BizTalk Mapper, drop the first functoid onto the Mapper surface. Double-click it on the Mapper surface to show a property sheet. On the General tab, notice that exactly three parameters are allowed. Manually add three constants, as in Figure 18.8. In the previous macros, the first functoid also has exactly three FUNCTOID_IN macros.

Figure 18.8. Functoid with exactly three input parameters.


Back in BizTalk Mapper, the Script tab has the JScript implementation shown in Figure 18.9. In the previous macros, the FUNCTOID_IMPL macro identifies JScript as the language and gives the name of the CSelector member function that calculates and returns JScript as a string.

Figure 18.9. Functoid script implementation.


Repeat the steps for the second functoid. The differences here are the use of VBScript and a variable number of input parameters. In the previous macros for the second functoid, the FUNCTOID_IMPL macro references VBScript and a second CSelector member function. There is also only one FUNCTOID_IN macro for the one required input parameter. The macro FUNCTOID_IN_OPTIONAL flags that any number of additional parameters is valid.

Adding Custom Functionality

We are ready to add the specifics of the Selector functoid to the implementation. In the process, we will examine the map-based implementation in detail.

BEGIN_FUNCTOID_MAP / END_FUNCTOID_MAP

These two macros delimit the start and end of the functoid map.

BEGIN_FUNCTOID_MAP(CSelector, _VERSION, FUNCTOID_FIRST_USER_FUNCID) 
...
END_FUNCTOID_MAP

BEGIN_FUNCTOID_MAP has three arguments. The first argument is the name of the C++ class implementing the COM coclass. The second argument is the integer version to report to BizTalk Mapper. The last argument is the FUNCID for the first functoid in the map.

Each functoid must have a unique id, or FUNCID. BizTalk reserves IDs 0 to 1000 so that the first available ID for user-defined functoids is 1001, the value of FUNCTOID_FIRST_USER_ID. IFunctoidDispatchImpl assigns FUNCIDs F, F+1, F+2, and so on to each of the functoids in the map where F is the last argument to BEGIN_FUNCTOID_MAP.

No changes are required here for the Selector implementation.

BEGIN_FUNCTOID/END_FUNCTOID

These two macros delimit the start and end of each functoid within the map. Use one pair of these macros for each functoid in an ATL implementation. The ATL Object Wizard produces two pairs. These macros must appear between the BEGIN_FUNCTOID_MAP/END_FUNCTOID_MAP pair.

The Selector implementation needs just one functoid. Delete the first one in the map because the second one better fits our remaining needs. To do so, delete from the first BEGIN_FUNCTOID to its matching END_FUNCTOID. Also, change the first parameter of the remaining BEGIN_FUNCTOID to "Selector".

BEGIN_FUNCTOID(L"Selector", FUNC_CATEGORY_UNKNOWN) 
...
END_FUNCTOID

BEGIN_FUNCTOID has two arguments. The first argument is the name BizTalk Mapper should use for the functoid in the GUI. This value must be a wchar_t based string.

The second argument is the function category. BizTalk Mapper uses the category to determine which tab on the Functoid Palette should contain the custom functoid. The value FUNC_CATEGORY_UNKOWN means the Advanced tab. Table 18.4 shows the categories assigned to each tab on the Functoid Palette. BizTalk does not allow you to create new tabs. For reference, the table also has what functoids BizTalk provides by default in each category.

Table 18.4. Functoid Categories on Tool Palette
TabCategory ValueBizTalk Functoids
StringFUNC_CATEGORY_STRINGFind, Left, Lowercase, Right, Length, Extract, Concatenate, Left Trim, Right Trim, Uppercase
MathematicalFUNC_CATEGORY_MATHAbsolute Value, Integer, Max, Min, Modulo, Round, Square Root, Add, Subtract, Multiply, Divide
LogicalFUNC_CATEGORY_BOOLEANGreater, GreaterEqual, Less, LessEqual, Equal, NotEqual, IsString, IsDate, IsNumeric, Or, And, Existence
Date/TimeFUNC_CATEGORY_DATETIME_FMTAdd Days, Date, Time, Date and Time
ConversionFUNC_CATEGORY_DATACONVASCII from Char, Char from ASCII, Hex, Octal
ScientificFUNC_CATEGORY_SCIENTIFICArc Tangent, Cosine, Sine, Tangent, Natural Exp, Natural Log, Exp, Log, X^Y, X Log Y
CumulativeFUNC_CATEGORY_CUMULATIVESum, Average, Group Min, Group Max, Group Concatenate
Database[*] FUNC_CATEGORY_DBLOOKUPDatabase Lookup, Error Return
 [*] FUNC_CATEGORY_DBEXTRACT Value Extractor
AdvancedFUNC_CATEGORY_SCRIPTERScripting
 FUNC_CATEGORY_INDEXIndex
 FUNC_CATEGORY_UNKNOWN(none)
 [**] FUNC_CATEGORY_COUNTRecord Count
 [**]FUNC_CATEGORY_VALUE_MAPPINGValue Mapping, Value Mapping Flattening
 [**]FUNC_CATEGORY_LOOPINGLooping
 [**]FUNC_CATEGORY_ITERATIONIteration

[*] BizTalk 2002 recommends not using the Database tab.

[**] Not supported in BizTalk 2002.

These category values are from the enum FUNC_CATEGORY from the BizTalk SDK.

FUNCTOID_TOOLTIP

This macro specifies the ToolTip for BizTalk Mapper to use for a functoid. This macro must appear between a BEGIN_FUNCTOID/END_FUNCTOID pair exactly once.

The Selector implementation needs to adjust the ToolTip from the generic one produced by the ATL Object Wizard. Change the macro to read as follows:

FUNCTOID_TOOLTIP(L"Select one value from a list of values.") 

FUNCTOID_TOOLTIP has one argument, and it must be a wchar_t based string. The ToolTip is typically a one-line description. Refer to Table 18.2 for a list of sample ToolTips used by BizTalk.

FUNCTOID_BITMAP

This macro specifies the resource ID of an image for a functoid. BizTalk Mapper will use the image on the Functoid Palette. This macro must appear between a BEGIN_FUNCTOID/END_FUNCTOID pair exactly once.

The Selector implementation does not need to modify this entry:

FUNCTOID_BITMAP(IDB_FUNCTOID_SELECTOR) 

The resource ID IDB_FUNCTOID_SELECTOR refers to a blank image. The image is 16×16 and matches the color scheme of the icons on the Advanced tab.

There are no requirements in the BizTalk documentation for matching the color scheme of images used by BizTalk. If you elect to follow the color scheme, the image FunctoidBlanks.bmp provides blanks for all tabs used in BizTalk Mapper. The ATL Object Wizard adds this image for convenience. Each blank follows the background color and 3D shading colors used by BizTalk Mapper. Refer to Table 18.2 for a list of the RGB colors of each tab.

FUNCTOID_IN and FUNCTOID_IN_OPTIONAL

These macros specify input parameters for a functoid. These macros must appear between a BEGIN_FUNCTOID/END_FUNCTOID pair.

FUNCTOID_IN works in concert with FUNCTOID_IN_OPTIONAL. Use FUNCTOID_IN to specify required input parameters. Use FUNCTOID_IN_OPTIONAL to specify that a variable number of parameters is allowed. For example, if a functoid has exactly two parameters, use FUNCTOID_IN twice. If a functoid has at least two parameters, then use FUNCTOID_IN twice and FUNCTOID_IN_OPTIONAL once.

The Selector implementation has one or more input parameters. The ATL Object Wizard output is exactly what we require.

FUNCTOID_IN(CONNECT_TYPE_ALL_EXCEPT_RECORD) 
FUNCTOID_IN_OPTIONAL(CONNECT_TYPE_ALL_EXCEPT_RECORD)

Both macros have a single input parameter. It is the bitwise-or of a set of connection flags. Each flag specifies to BizTalk Mapper a diagram element that is valid to connect to the functoid as the input parameter. The value CONNECT_TYPE_ALL_EXCEPT_RECORD means that any diagram element can serve as the input to the functoid except the non-leaf nodes, also known as records, in the source document.

FUNCTOID_IN_OPTIONAL works the same as FUNCTOID_IN except that it specifies one set of connection flags for all optional parameters. Note that the BizTalk SDK does not require that all optional parameters have the same flags. Rather, the map-based implementation provided by this book has the limitation for simplicity.

In general, BizTalk Mapper must decide whether a line may connect two elements on the diagram. Each element carries a set of connection flags. A connection is allowed if there is a non-empty intersection between the two sets of flags. These lines specify the flow of information through input and output parameters.

Table 18.5 shows which diagram elements each connection flag specifies.

Table 18.5. Diagram Elements for Each Connection Flag
Connection FlagDiagram Elements Specified
CONNECT_TYPE_NONENo element.
CONNECT_TYPE_FIELDLeaf nodes in the document specification. In XML, leaf nodes are attributes and tags with no subtags.
CONNECT_TYPE_RECORDNon-leaf nodes in the document specification. In XML, non-leaf nodes are tags that have subtags. Using this connection flag is meaningful only if the XML tag can have both subtags and values, such as text. This connection type for an input parameter, for example, means to input the tag's values, not its subtags.
CONNECT_TYPE_RECORD_WITH_CONTENTSimilar to CONNECT_TYPE_RECORD. The exact difference is not documented.
CONNECT_TYPE_XXX where XXX = STRING, MATH, SCIENTIFIC,DATACONV, DATETIME_FMT, BOOLEAN,SCRIPTER, COUNT, INDEX, CUMULATIVE, VALUE_MAPPING, LOOPING, ITERATION, DBLOOKUP, DBEXTRACTOutput from a functoid in category FUNC_CATEGORY_XXX. Example: CONNECT_TYPE_FUNC_STRING corresponds to output from functoid category FUNC_CATEGORY_STRING.
CONNECT_TYPE_ALLAll elements on the diagram.
CONNECT_TYPE_ALL_EXCEPT_RECORDAll elements on the diagram except non-leaf nodes.

These category values are from the enum CONNECTION_TYPE from the BizTalk SDK.

For example, to restrict an input parameter to accept data from leaf nodes in the document, mathematical functoids, and scientific functoids, use the bitwise-or of CONNECT_TYPE_FIELD, CONNECT_TYPE_FUNC_MATH, and CONNECT_TYPE_SCIENTIFIC. No other element will be allowed to connect to the functoid.

FUNCTOID_OUT

This macro specifies the output parameter for a functoid. A functoid must have exactly one output parameter.

FUNCTOID_OUT works identically to FUNCTOID_IN except that exactly one occurrence of FUNCTOID_OUT is required.

The Selector implementation uses the FUNCTOID_OUT macro provided by the ATL Object Wizard.

FUNCTOID_OUT(CONNECT_TYPE_ALL) 

This macro means that connecting the output of the functoid to another element is not restricted at all by this functoid.

FUNCTOID_IMPL

This macro specifies the script-based implementation for the functoid. This macro must appear between a BEGIN_FUNCTOID/END_FUNCTOID pair exactly once.

The Selector implementation needs to adjust the script from the generic one produced by the ATL Object Wizard. Change the macro to read as follows:

FUNCTOID_IMPL(SCRIPT_CATEGORY_JSCRIPT, GetSelectorImpl) 

FUNCTOID_IMPL has two arguments. The first argument specifies the type of script. Use the value SCRIPT_CATEGORY_JSCRIPT or SCRIPT_CATEGORY_VBSCRIPT from the enum SCRIPT_CATEGORY in the BizTalk SDK. That enum also has the value SCRIPT_CATEGORY_XSLSCRIPT, which is currently not supported.

The second argument is a member function of CSelector. Strictly speaking, it is a member function of the class name passed as the first argument to the BEGIN_FUNCTOID_MAP macro. To complete this change started previously, rename the member function CSelector::GetFunctoidImpl1 to CSelector::GetSelectorImpl in both the .h file and .cpp file. At the same time, delete the member function CSelector::GetFunctoidImpl2; it is no longer needed.

The signature of the function needs to be BSTR CSelector::GetSelectorImpl(long). The parameter to this function will be the number of actual parameters specified on the diagram for the functoid. The GetSelectorImpl() function can use that number to calculate a script specific to that number and return a string allocated by SysAllocString().

The JScript implementation appears earlier in the section “Script Implementation” and is incorporated in Listing 18.3. To complete the Selector implementation, replace the body of CSelector::GetSelectorImpl() with the JScript. Notice that the implementation uses the nParameters argument to format how many parameters the JScript function should take. Rebuild the project.

Listing 18.3. Implementation of CSelector::GetSelectorImpl() (Selector.cpp)
BSTR CSelector::GetSelectorImpl ( long nParameters )
{
   // format function signature :  function Selector (j, val1, ..., valn)
   wchar_t wsz[4096];
   wcscpy(wsz, L"function Selector ( ");
   if (nParameters > 0) ::wcscat(wsz, L"j");
   for ( int i = 1;  i < nParameters;  ++ i )
      swprintf(wsz+::wcslen(wsz), L", val%d", i);
   wcscat(wsz, L" )
");

   // append function body
   wcscat(wsz,
      L"{
"
      L"    try
"
      L"    {
"
      L"        // Normalize the various forms of the input to an integer
"
      L"        switch (j.constructor) // let's check the type
"
      L"        {
"
      L"            case Number:  // make sure any number is integer
"
      L"                j = Math.floor(j); // e.g., use 1 if have 1.1
"
      L"                break;
"
      L"            case Boolean: // use 1 for true and 2 for false
"
      L"                j = j ? 1 : 2;
"
      L"                break; 
"
      L"            case String: // string could hold bool or number
"
      L"                switch (j.toUpperCase()) 
"
      L"                {
"
      L"                    case "TRUE":  j = 1; break;
"
      L"                    case "FALSE": j = 2; break;
"
      L"                    default: j = Math.floor(parseInt(j)); break;
"
      L"                 }
"
      L"                 break;
"
      L"            default:  
"
      L"                j = 0; // flag error with invalid index
"
      L"                break;
"
      L"        }
"
      L"        
"
      L"        // return the selected value, if any
"
      L"        return eval('val' + j);
"
      L"    }
"
      L"    catch ( x )
"
      L"    {
"
      L"    }
"
      L"    return ""; // error return
"
      L"}
");
   return SysAllocString(wsz);
}

Debugging the Functoid

Debugging the functoid is a simple matter of running BizTalk Mapper under the debugger. Return to the Visual C++ IDE that has the project for the custom functoid. Select Project, Settings from the menu and select the Debug tab on the property page that opens. Select a debug configuration in the Settings For combo box. Enter the full path to BIZMAP1.EXE in the Executable for Debug Session edit box. The full path is typically C:Program FilesMicrosoft BizTalk ServerXML ToolsBizMap1.EXE. Click OK.

Start the Visual Studio debugger for the functoid project, and BizTalk Mapper launches. Breakpoints will fire inside the functoid component as you create a map and exercise the functoid.

Bear in mind that debugging the functoid does not actually debug the functoid's script. The debugger steps through the COM component. Debugging the script must be done separately outside the Visual C++ IDE.

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

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