Friday, October 29, 2010

A Basic Interface - Web Service Workflow

Just about every interface consists of two basic components: the integration object(s) and the workflow or business service. I will demonstrate a workflow approach which will give you more opportunity to customize down the road.

It is here that we begin to differentiate the integration by the communication mechanism. Because I am designating this integration as a Web Service, that will drive the type of data this workflow will expect as an input and output. The workflow I build will eventually be exposed as a WSDL to be consumed by an external program. That WSDL should have the definition of the message it is expecting, in this case, the XSD, or definition of the Integration Object we just built. How we accomplish this is to set the Input Process Property to a Data Type of 'Integration Object' and to actually specify the integration object we built, in the Integration Object attribute of the process property.


You can also see my place holder for the SR Number that I want to return to the external system in the response message. The 'IncomingXML' property is already in the format needed to be passed to the EAI Siebel Adapter, so there is no conversion necessary. And we are assuming that the data being passed is exactly as it should be applied. You will create the following steps which I will explain (other than Start and End which are self explanatory):
The 'Upsert SR' is a Business Service calling 'EAI Siebel Adapter'. Now here is the another design decision to be made. Each of the available methods differentiate exactly how the data should be applied. But there are two broad determinations. If we were to use the Execute method, then the 'operation' element which exists in each component of the IO would be used to determine how the data should be applied. This gives more control the calling system (or a data map which I will discuss later). The other set of methods essentially comprise a One Size Fit All to applying all the data uniformly. I will use the latter approach here and set the method to 'Upsert'. There is only one component in my IO, so if it exists, it will be updated, otherwise it will be inserted. The input arguments for this step are the IncomingXML message from the external system and a parameter telling the EAI Siebel Adapter to create the Status Object.

There is one Output Argument. We no longer care about the input message at this point because it will have been applied so we just overwrite it with the return, which in this case will be the status key.
The last step in the WF is another Business Service step calling 'PRM ANI Utility Service', 'GetProperty' method. This business service has a plethora of useful methods for manipulating property sets. This particular method will extract the value of a field from an integration object instance. Here are the inputs:
The output is to set the process property 'SRNumber' with the Output Argument, 'Property Value'. When the return message is sent back to the calling system, this property will exist with the generated SR Number.

Simulating/Troubleshooting this WF from within tools is difficult as built so I sometimes add a bypass step off the start branch to read the integration object from a file. I may talk about this later but want to keep this post pretty straightforward. So for now, this workflow can just be deployed, checked in and activated.

A Basic Interface - Building the Integration Object

I am not sure how easy it will be to summarize EAI in a couple of blog posts as there are definately a lot of ifs and buts in the design process. Nevertheless, I think it wold be useful to show how to build a basic interface using a couple of different techniques. Frequently you client's enterprise architecture will drive which to use.

Integration generally takes one of three forms
  • Query - Returns a data set of source data to be displayed in the target system
  • Schema Update - Takes a hierarchical data structure and applies it to the target system
  • Functional Action - Triggers a service to perform some set of business rules
There is perhaps some overlap here and any of these can be inbound or outbound to siebel, but this is a general way of categorizing your interfaces. And within each there are several different ways to implement more specific requirements.

Regardless of approach, the basic component of most interfaces is the structure of how data is viewed or applied. Let's say we need to Upsert a Service Request. A Schema Update assumes a hierarchical organization of data using the Integration Object data structure. Bookshelf provides extensive instruction on how these are built and configured to achieve certain goals so I will only touch on the highlights.

First, create an Integration Object in Siebel: from the Tools File Menu, New Object Wizard, EAI Tab, choose Integration Object. In the wizard, select the Project and choose 'EAI Siebel Wizard' from the second dropdown, and click Next. For the purpose of this example, we can just use the Service Request business object as the source object and the root BC will be Service Request. Enter a name of your choosing and click Next. In the next wizard page, deselect all child objects for which there are no fields to set. In this case that will be all of them except for the root as the more objects and fields in the message, the longer it will take the various architecture components to parse and translate the message. Click Next, then Finish on the next page.

Your Integration Object has been created. The next step is to verify the user keys. An integration object needs to have a valid user key in order to do an upsert. This basically specifies which key fields to use to find a record to update. In my example for Service Request, a key was not generated by the wizard so I will create one. Navigate to Integration Component Key in the explorer under the Service Request Integration Component. Create a new record, provide a name, set the sequence number to 1 and the key type to 'User key'. Create a child record in Integration Component Key Fields, provide a name and set the Field Name to 'Id'.

Another optional step we will use in this example is the Status Key. After creating a service request, I want to return the service request number to the external system as verification of success and so this SR can be referenced later by the customer. To do this we use the Status Key. This is basically a structure of the data set we wish to return from the EAI Siebel Adapter call and pass back to the calling system. A Status Key can be specified for each Integration Component so the final data set is the structure of all the keys combined hierarchically. In this case, navigate to Integration Component Key in the explorer under the Service Request Integration Component, create another new record, provide a name, 'StatusKey, set the sequence number to 1 and the key type to 'Status key'. Create a child record in Integration Component Key Fields, provide a name and set the Field Name to 'SR Number'.

Finally, while not absolutely necessary, you should inactivate all fields you are not using for each Integration Component. For an inbound upsert to Siebel, the calling system does not need to provide all the fields that are active in the IO schema, but if the field is active in the IO, then the external system Could send that data element which may have undesired affects depending on the interface. Make sure all fields used in the key are activated as well as all fields being passed from the external system. Unlike BC field lengths, the length property of an Integration Component Field is more important as when an XSD is generated and provided to the external system, this property will frequently be used by the web development tool to validate the data entered into that field. You can also change the XML Tag attribute to a label recognized by the external system (so long as spaces are removed).

One thing to keep in mind is that if an insert is desired, then the calling system should just pass a constant to the user key field, 'Id' so that Siebel will not find a record and a new one will be created. A value like '_New_Record_' is a safe value because the '_' will never be part of a generated row id.

Tuesday, August 17, 2010

Common (or not) eScript Syntax Errors

I would love to post a comprehensive list of gotchas, but then that would make them not gotchas if you know what I mean as I would know them all. So instead, I will mention what sidelined me for several hours last night and hope to spur some discussion about what other people have come across. If I think of others over time, I will try to update this post.

Space after the function name. I had copied and pasted some functions from somewhere else in my client's repository and the functions had no space between the name and the opening parenthesis of the passed variable declarations. I was not (and I guess still am not) aware of a limitation in this regard, but I saw all sorts of strange behavior afterward. Namely, the calls to these functions seemed to be ignored which took me a long time to realize. They seem to work fine in their original home elsewhere in the repository so this may be related to context, but suffice to say this is some thing to think about when troubleshooting.

Friday, July 23, 2010

My Barcode Promised Land

The effort of trial and error, traversing dead ends, and determining what I could not do, led me eventually to what I could. Let me start by saying that if I was a Siebel engineer (completely unaware of what constraints they had to work with) I would have provided an Application level method called something like BarcodeScan that could be trapped. I could then put a runtime event on it and trigger a wokflow when I was done. But then again I also would not have coded in the limitations I mentioned earlier.

Barring all that, I still needed a couple of basic things:
  • Hook to trigger additional functional logic
  • Do lookups on Serial Numbers
Additionally, it would be nice to:
  • Minimize the number of clicks
  • Do lookups on the child record of a BC
  • Parse the input so that I could do different stuff based on the type of data
Given those must-haves and nice-to-haves, I decided to hack the business service, trap the methods in question and just do my own thing. I should mention, that my initial approach was more from a wrapper perspective than a replace perspective. That is, I thought I could just trap the method, do my stuff, then continue with the vanilla method. Here is the problem though. Since everything that happens in the vanilla method threads occurs out of the GUI context, I cannot leverage any Active... methods. Therefore to do something as simple as update the record returned by the vanilla lookup, I would have to requery for it in my own objects to get it in focus to update it. Well if I am requerying for it, what is the point of doing the same query twice? I can just do my own query once in the Active object and then trigger my post events.

Let me start by walking through the most important Must-Have

Hook to trigger additional functional logic
I have sort of hinted at how this was achieved in general. Once I realized that the 'HTML FS Barcoding Tool Bar' was getting called, I modified the server script on this service to log when its methods are called. The important method here is 'ProcessData' which is the one method called regardless of the processing mode in use. At this point you have the barcode data and the Entry mode. You can also determine what view you are on via ActiveViewName. I trapped the Find, New and Update methods in the PreInvokeMethod event to store the current processing mode in profile attribute:
switch (MethodName) {
case "Find":
case "New":
case "Update":
TheApp.SetProfileAttr("BarcodeProcessMode", MethodName);
break;
}
With these three fields, the View, Process Mode, and Entry Mode, I can query the FS Barcode Mappings BC for a unique record.

boBCMappings = TheApp.GetBusObject("FS Barcode Mappings");
bcBCMappings = boBCMappings.GetBusComp("FS Barcode Mappings");
with (bcBCMappings) {
ClearToQuery();
SetViewMode(AllView);
ActivateField("Field");
ActivateField("Applet BC");
SetSearchSpec("View", sView);
SetSearchSpec("Entry Mode", sEntryMode);
SetSearchSpec("Process Mode", sProcessMode);
ExecuteQuery(ForwardOnly);
bFound = FirstRecord();

if (bFound) {
...
What I want to get from that record for now is the lookup field. I also need to know the Active BC to do the lookup in. Again, I cannot use ActiveBusComp or ActiveApplet so I just added a join to the FS Barcode Mappings BC to the repository S_APPLET table based on the applet name already stored in the Admin BC and added a joined field based on S_APPLET.BUSCOMP_NAME. I still feel like there is a better way to do it, but that is where I am at right now. Anyway, from the admin record I have a BC to instantiate, a field to set a search spec on, and the text value of the search spec.
sField = GetFieldValue("Field");
sBusComp = GetFieldValue("Applet BC");

boObject = TheApp.ActiveBusObject();
bcObject = boObject.GetBusComp(sBusComp);
with (bcObject) {
ClearToQuery();
SetViewMode(AllView);
ActivateField(sField);
SetSearchSpec(sField, sLogicalKey);
ExecuteQuery(ForwardOnly);
bFound = FirstRecord();

if (bFound) {
...
My client has multiple barcode processes so all this could be happening in different places. So the last step is to add some logic to branch out my hook. I am using the BC for now but we could make this more robust:
switch (sBusComp) {
case "Service Request":
ProcessSR();
break;

case "Asset Mgmt - Asset":
ProcessAsset();
break;
}

The Dead Ends of Barcode Hacking

Most technical blog posts are about solutions. Since this series on Barcodes is also about my journey, I thought it might be interesting to also talk about what I tried out but did not work. Who knows, maybe I can save someone the effort of trying these. Or perhaps the patterns I am finding through these dead ends will help someone head off into a totally new direction as it has helped me.

Auto Enabling
So the first thing I though would be cool would be to auto enable the Barcode tool bar and the natural place to do this seemed to be on the Application Start event. After a lot of trial and error, my application kept crashing after trying to invoke the 'Active' method. The 'Active' method receives as an input the Active View Name and Active Applet Name. The startup page is not actually instantiated yet when the Application Start event executes so even hard coding a startup page into the input property set results in an application crash. So Application Start is not the right place.

Applet Context
When trying to call various barcode service methods through script, many of them require the applet name as an input parameter. Trying to use ActiveApplet though results in an error you would typically receive when you are not in a GUI context, such as when using EAI. ActiveViewName does work though so it is only the applet. I think what is happening is that clicking on a toolbar button, even though an applet appears to remain in focus (via the color pattern of the applets) focus is actually on the toolbar and hence active applet does not work. Well that is my theory anyway.

Default to Find Mode
My client will mainly be using the Find process mode so I thought it would be good that if I could not Auto Enable the tool bar, at least I could auto default the toolbar to Find mode once it is enabled. So I trapped the Active method on the business service and called the Find method from the InvokeMethod event after the Active method runs. But this does not quite work. If I click the Enable button twice though it does. It appears that this is a context issue. It is as if GUI context has been returned to the user prior to the Find script executing.

I noticed that a series of barcode events trigger anyway when the Application starts. I therefore tried triggering my auto enable scripts from the tail end of one of these events, again through the InvokeMethod event, but again ran into the context issue.

SWE From Script
The interesting thing to me is that the input parameters to all of these methods are a series of SWE Commands, Methods and parameters. It seems as though another browser thread or frame is being used where SWE commands are the language Siebel uses to initiate the logic. There is probably a way to call a SWE command directly through script but I am not aware of it. What I am thinking is to use SWE command to refresh the context of the GUI thread after a Barcode method has been called, then to explicitly call a followup method. I cannot do this directly as the results of the second method call appear to get lost as the context has been returned to the GUI before the second call.

Thursday, July 22, 2010

Hacking the 'HTML FS Barcoding Tool Bar' Business Service

In case you were curious what happens in the black box, once the Barcode toolbar is up and running, here is a dump of the Input and Output property sets from each Method that is called:

When the application starts up, the 'IsBarcodeEnabled' method is called about 15 times, is passed an empty property set and returns:
01  Prop 01: IsBarcodeEnabled           / 1

Also on startup, the 'ResetButton' method is called which appears to set the set which buttons on the toolbar are turned on or off and which buttons are active. Resetting them makes the enable button Active and off, and the process mode buttons inactive and off, as you can see from the outputs. Here are the Inputs:
01  Prop 01: SWECmd                      / InvokeMethod
01 Prop 02: SWEMethod / ResetButton
01 Prop 03: SWEService / HTML FS Barcoding Tool Bar
01 Prop 04: SWERPC / 1
01 Prop 05: SWEC / 1
01 Prop 06: SWEIPS / @0*0*0*0*0*3*0*

And these Outputs:
01  Prop 01: NEW_ENABLED                 / N
01 Prop 02: ACTIVE_ENABLED / Y
01 Prop 03: ACTIVE_CHECKED / N
01 Prop 04: UPDATE_CHECKED / N
01 Prop 05: FIND_ENABLED / N
01 Prop 06: FIND_CHECKED / N
01 Prop 07: NEW_CHECKED / N
01 Prop 08: UPDATE_ENABLED / N

The control keys are then determined. First the 'GetStartKeyCode' method is called with these Inputs:
01  Prop 01: SWECmd                      / InvokeMethod
01 Prop 02: SWEMethod / GetStartKeyCode
01 Prop 03: SWEService / HTML FS Barcoding Tool Bar
01 Prop 04: SWERPC / 1
01 Prop 05: SWEC / 2
01 Prop 06: SWEIPS / @0*0*0*0*0*3*0*

And these Outputs:
01  Prop 01: KeyCode                     / 220

Lastly, the End key via the 'GetEndKeyCode' method with these Inputs:
01  Prop 01: SWECmd                      / InvokeMethod
01 Prop 02: SWEMethod / GetEndKeyCode
01 Prop 03: SWEService / HTML FS Barcoding Tool Bar
01 Prop 04: SWERPC / 1
01 Prop 05: SWEC / 3
01 Prop 06: SWEIPS / @0*0*0*0*0*3*0*

And these Outputs:
01  Prop 01: KeyCode                     / 220

Clicking the enable button triggers the 'Active' method has these Inputs:
01  Prop 01: SWEActiveView               / All Service Request List View
01 Prop 02: SWECmd / InvokeMethod
01 Prop 03: SWEMethod / Active
01 Prop 04: SWEActiveApplet / Service Request List Applet
01 Prop 05: SWEService / HTML FS Barcoding Tool Bar
01 Prop 06: SWERPC / 1
01 Prop 07: SWEC / 22
01 Prop 08: SWEIPS / @0*0*0*0*0*3*0*

and these Outputs:
01  Prop 01: OPTION0                     / Service Request
01 Prop 02: NEW_ENABLED / Y
01 Prop 03: OPTION2 / Repair
01 Prop 04: ACTIVE_ENABLED / Y
01 Prop 05: ACTIVE_CHECKED / Y
01 Prop 06: OPTION3 / Pick Ticket
01 Prop 07: UPDATE_CHECKED / N
01 Prop 08: OPTION6 / Serial #
01 Prop 09: FIND_ENABLED / Y
01 Prop 10: Check / 1
01 Prop 11: OPTIONS_LENGTH / 7
01 Prop 12: OPTION4 / Order
01 Prop 13: FIND_CHECKED / Y
01 Prop 14: OPTION5 / Product
01 Prop 15: NEW_CHECKED / N
01 Prop 16: OPTION1 / Asset #
01 Prop 17: UPDATE_ENABLED / Y

Clicking the Find button gives you these Inputs:
01  Prop 01: SWEActiveView               / All Service Request List View
01 Prop 02: SWECmd / InvokeMethod
01 Prop 03: SWEMethod / Find
01 Prop 04: SWEActiveApplet / Service Request List Applet
01 Prop 05: SWEService / HTML FS Barcoding Tool Bar
01 Prop 06: SWERPC / 1
01 Prop 07: SWEC / 11
01 Prop 08: SWEIPS / @0*0*0*0*0*3*0*

And these Outputs:
01  Prop 01: OPTION0                     / Service Request
01 Prop 02: NEW_ENABLED / Y
01 Prop 03: OPTION2 / Repair
01 Prop 04: ACTIVE_ENABLED / Y
01 Prop 05: ACTIVE_CHECKED / Y
01 Prop 06: OPTION3 / Pick Ticket
01 Prop 07: UPDATE_CHECKED / N
01 Prop 08: OPTION6 / Serial #
01 Prop 09: FIND_ENABLED / Y
01 Prop 10: Check / 1
01 Prop 11: OPTIONS_LENGTH / 7
01 Prop 12: OPTION4 / Order
01 Prop 13: FIND_CHECKED / Y
01 Prop 14: OPTION5 / Product
01 Prop 15: NEW_CHECKED / N
01 Prop 16: OPTION1 / Asset #
01 Prop 17: UPDATE_ENABLED / Y

Clicking the New button (on the toolbar) gives you these Inputs:
01  Prop 01: SWEActiveView               / All Service Request List View
01 Prop 02: SWECmd / InvokeMethod
01 Prop 03: SWEMethod / New
01 Prop 04: SWEActiveApplet / Service Request List Applet
01 Prop 05: SWEService / HTML FS Barcoding Tool Bar
01 Prop 06: SWERPC / 1
01 Prop 07: SWEC / 23
01 Prop 08: SWEIPS / @0*0*0*0*0*3*0*

And these Outputs:
01  Prop 01: OPTION0                     / Serial Number Entry
01 Prop 02: NEW_ENABLED / Y
01 Prop 03: ACTIVE_ENABLED / Y
01 Prop 04: ACTIVE_ENABLED / Y
01 Prop 05: UPDATE_CHECKED / N
01 Prop 06: FIND_ENABLED / Y
01 Prop 07: Check / 1
01 Prop 08: OPTIONS_LENGTH / 1
01 Prop 09: FIND_CHECKED / N
01 Prop 10: NEW_CHECKED / Y
01 Prop 11: OPTIONS_LENGTH / 7

Clicking the Update button (on the toolbar) gives you these Inputs:
01  Prop 01: SWEActiveView               / All Service Request List View
01 Prop 02: SWECmd / InvokeMethod
01 Prop 03: SWEMethod / Update
01 Prop 04: SWEActiveApplet / Service Request List Applet
01 Prop 05: SWEService / HTML FS Barcoding Tool Bar
01 Prop 06: SWERPC / 1
01 Prop 07: SWEC / 24
01 Prop 08: SWEIPS / @0*0*0*0*0*3*0*

And these Outputs:
01  Prop 01: OPTION0                     / Asset
01 Prop 02: NEW_ENABLED / Y
01 Prop 03: ACTIVE_ENABLED / Y
01 Prop 04: ACTIVE_ENABLED / Y
01 Prop 05: UPDATE_CHECKED / Y
01 Prop 06: FIND_ENABLED / Y
01 Prop 07: Check / 1
01 Prop 08: OPTIONS_LENGTH / 1
01 Prop 09: FIND_CHECKED / N
01 Prop 10: NEW_CHECKED / N
01 Prop 11: UPDATE_ENABLED / Y

And perhaps the most important one, scanning the data. This executes the 'ProcessData' method and would occur after the second end control character is received from the scanner. The Inputs are:
01  Prop 01: OPTION                      / Service Request
01 Prop 02: BARCODE / 2-7144002

And these Outputs:
01  Prop 01: Applet Name                 / Service Request List Applet

Keep in mind that in many cases, the actual property values are based on data pulled from the 'FS Barcode Mappings' BC.

Spelunking in the Barcode Cavern

My new client would like to use a Barcode scanner for a whole variety of Field Service applications:
Shipping Label to Lookup and RMA Order and update some fields
Asset Label to Lookup or Create an RMA Order Line Items and update some fields
Asset Label to Lookup a Repair record and update some fields

Siebel Bookshelf and Supported Platforms provides some basic information. There are a couple of approaches to using a Barcode scanner
  • Treat it like any data entry device. In other words, you prepare your record (click new, Clear to Query, etc.), click into a field, scan your barcode, the scanner copies the translated barcode value to the field, then you do what you want (save the record, execute query, etc).
  • Use the Barcode ToolBar. This has some basic modes (New, Update, Find), an administration area that ties a View to one or more modes and a field. So when you navigate to a view, Siebel (when the barcode toolbar is turned on through an object manager parameter), checks to see of any barcode admin records exist for that view and the currently selected mode. If so these appear in a dropdown in the toolbar that a user can select a value from. If the User then scans something, the Application "Processes" the barcode depending on the mode, either doing a query based on a specified field, updating a field on the current record, or creating a new record and populating a specified field.
This sounds groovy until you hear about some of the limitations and start thinking about a more realistic process. So here are the limitations as I understand them:
  • Only some Barcode Types (think fonts) are supported.
  • The processing can only occur in the primary BC of the BO, or the Parent BC in a master detail view.
  • Serial Numbers cannot be looked up (I am still investigating why this is but I am guessing it has to do with them possibly not being unique).
  • Only barcode scanners that support using customizable control character before and after the scanned input will work
  • A single input value is taken (so no splitting of a concatenated value)
  • You basically have to tell the toolbar what value to expect (again, no intelligent parsing)
Prototyping:
  • Insure you have the Field Service, and Barcode license keys
  • In the Field Service cfg file (if using a thick client), set the ShowBarcodeToolbar parameter to TRUE. Intuitively enough, this will make the Barcode toolbar appear in your app upon restart.
  • Click the enable button (far right hand button) on the toolbar
  • As you navigate to a view, the application will perform a query of the 'FS Barcode Mappings' BC, or S_BC_ENTRY_TRGT table for admin records corresponding to the current view and the currently selected processing mode (the three buttons to the left of the dropdown in the toolbar each correspond to a different mode). If you think about it, this is sort of similar to how Actuate reports are tied, except you can actually administer this a bit in the GUI.
  • We can mimic a barcode scan by using <ctrl-\>, followed by the translated value we are trying to scan (SR number for instance), followed by another <ctrl-\>
  • If you want to use different control character than <ctrl-\> (because maybe that one is already taken or something), these are set on the 'HTML FS Barcoding Tool Bar' business service as User Properties. I will leave them be.
So in my real life example, I will:
  1. Navigate to All Service Requests
  2. Click Enable on toolbar
  3. Click the right most left side button on the toolbar, 'Find'
  4. Leave the dropdown as 'Serial Number'
  5. Hit <ctrl-\>
  6. Type in an SR # I can see in the list
  7. Hit <ctrl-\> again
  8. The Application should query for the SR # I entered
I am now going to dive figuring out a better way to customize this behavior. I'll be back.