Thursday, April 23, 2009

Workflow vs. Server Script

There's a lot of advice out there that says to use declarative solutions whenever possible and turn to scripting as a last resort only.  Hell, it's the basis of Siebel's technical document "Declarative Alternatives to Siebel Scripting".  I believe in using straight Tools configuration as much as possible, but there's one thing I'm not yet sold on: Using workflow processes in lieu of scripted business services for complex actions.

I used to believe what I heard, that workflow processes were superior to scripted business services - but then I decided to evaluate it for myself.  There are advantages and disadvantages to each approach, and I'll get to those later.

What originally sold me was the argument that workflow processes performed more strongly than server script because they did not have the overhead of a script interpreter.  The argument usually goes something like this, "Workflow processes run the compiled C++ code directly, so are faster than scripted processes that must be interpreted."  Well newsflash: Workflow processes also need to be interpreted.  Siebel did not build your workflow into their product, did they?  You described it using a tool, and that description must be interpreted at runtime.  Yes, compiled C++ is used to interpret your workflow processes.  And compiled C++ is also used to interpret your scripts.

Which interpreter is faster?  Which engine loads faster?  I ran some tests to find out.  The results show that the two were very comparible when performing the exact same operations.  If there were any differences, they were negligible.  My test updated the amount of a single revenue record.  Each process (scripted and workflow) did one query and one update.  After warming up the OM, each process completed in 57 milliseconds on average (which included the time to invoke each via a driver script).

What I did notice was that because workflow processes were not as descriptive as script, and you sometimes needed to do more work to accomplish the same task.  For instance, when I extended the test to update multiple revenue records, it took the scripted version 1 query and 21 writes to update 21 records, while the workflow processes needed 22 queries and 21 writes.  How did that affect the results?  The scripted process averaged a reasonable 500 milliseconds while the workflow process chugged along at 865 milliseconds on average.

So the results seem to say that workflows are just as fast as scripts when the performing the same operations, but the inflexibility of workflow processes can lead to algorithms that are less than ideal performance-wise.

Other reasons to like business services are that the development times are faster for various reasons.  The script debugging tools are more sophisticated, and starting your script debugger is typically faster than starting the workflow simulator - particularly if you need to deploy and activate a workflow subprocess before simulating.  And I don't think anyone would disagree when I say that scripts are far more flexible and powerful than workflow processes.

So what reasons are there to like workflow processes?  For one, they can be deployed without an SRF.  I'm guessing that benefit has not nor will ever be realized by a large portion of Siebel customers.  One reason that I like is that their level of instrumentation is far superior to script.  The workflow monitor and logging events are far more useful in a production environment than any script built-in instrumentation for script (there is almost none).  That can be invaluable in getting a production issue resolved quickly, for a problem that would normally be hard to reproduce and research.

In the end, I'm somewhere in the middle - but I'd have to say a bit closer to the script (read: dark) side.  What do you all think?

Thursday, March 12, 2009

Extending eScript Objects

There seemed to be a lot of interest in the post on Siebel Unleashed about the ABS (ATI) Framework for eScript (unrelated to Siebel's Appointment Booking System), which is some company's proprietary tool set used to perform repetitive tasks in eScript - some simple and some complex. A few examples given were: searching for the existence of a particular value in an array; accessing TheApplication() object; getting the field values of a record for which we know the Id. And doing those things in a consistent manner and using fewer keystrokes. What struck me is how well they integrated their framework with Siebel and core eScript objects.

I really believe in tool sets, and I'd like to see some Siebel hackers out there expand on the idea. How this tool set was built is not really obvious, so I'd thought I'd take a stab at it. Respecting the fact that the ATI Framework is proprietary, I won't give a whole lot of detail reverse-engineering the complex API calls I saw in the posts. However, I will show some basic techniques you can use to build your own framework using some simple examples. It would help to know a bit about objects and properties in JavaScript.

To keep the post relatively short, I'm not going to give any examples to validate that this stuff actually works. I'll leave that to you, because it's best to figure some things out yourself. If you're having problems, just know that I have successfully tested these concepts.

Is this value in the array?

Sometimes you have to search an array to see if a particular value exists. Nothing a little "for" loop can't take care of. But if you need to do this with any kind of frequency, why rewrite it every time? Also, what if a developer introduces a bug while trying to reimplement it? Write it once, make it work, and reuse it. Not only will you reduce your exposure to programming errors, but it will make your programs more succinct, and much easier to comprehend.

You can always add an application method that takes a reference to the array and the value you're searching for and returns a true or false, but I always wondered why this method wasn't part of the Array object to begin with. Let's go ahead and make it part of the Array object by modifying the prototype property to include a custom function. I put this in the "(declarations)" section of my application server script (which I chose because it's the first script I know of that's executed when the application starts):

Array.prototype.Exists = function (value)
{
  for (var i = this.length - 1; i >= 0; i--)
    if (this[i] == value) return true;
  return false;
}

Once this sets up you'll be able to see if the value "5 of spades" exists in your aCardDeck array: aCardDeck.Exists("5 of spades");

Accessing the Application Object

Ever get tired of typing TheApplication() every time you need to access an application method? Well those on the ATI Framework don't. It looks like they've reduced the application reference to some sort of global variable "TheApp", accessible from any object.

If I had a business component and wanted to make "TheApp" available from anywhere within that object, I could add a line like "var TheApp = TheApplication();" to the "(declarations)" section of the business component. But that means I'd have to add that line to every business component, which makes me angry. You won't like me when I'm angry. So how can we avoid the maintenance hassle, and me turning into a big green monster?

What if we update the prototype of the business component object to include "TheApp"? That sounds like a good idea. Well unfortunately I haven't been able to figure out what the business component object/constructor is actually called. I'll do the next best thing, which is to add "TheApp" to all custom objects. The business component object must be derived from the Object class. So here's another addition to the application "(declarations)":

Object.prototype.TheApp = this;

Remember, that "this" is the application object, since we're doing this from the application "(declarations)". This approach has the added benefit of enabling "TheApp" reference from all Siebel objects including business services, applets, and, yes, the application object itself. It surprised me that the application object was able to access "TheApp", since the prototype was modified after the application was already instantiated.

Adding a Custom Business Component Method

Let's say, for some reason, you constantly find the need to get business component field values in uppercase characters only. Well here's what I added to the application "(declarations)" this time:

Object.prototype.GetFieldValueUpperCase = function (sFieldName)
{
  return this.GetFieldValue (sFieldName).toUpperCase ();
}

Now if you have some bc "bcFoo", you can get the value for field "Bar" in uppercase characters by doing something like, "bcFoo.GetFieldValueUpperCase("Bar");" When this is called from a business component object, then all the references to "this" refer to that business component.

The unwanted side-effect here is that this method is enabled on all custom objects because we updated the prototype of the Object class. Since GetFieldValue() is only supported on business components, this will fail on any other object. So, if anyone finds the object/constructor names for any Siebel objects, please let that cat out of the bag. In any case,

I think you guys have had enough. Don't forget to let us all know if you find the object/constructor names for any Siebel objects. And if they're any new discoveries or breakthroughs out there, please comment about it. But for now, happy hacking!

Friday, February 13, 2009

Siebel 8.1.1.0 on Windows Vista

So I decided to see what this Siebel 8 hype was all about. I downloaded all the necessary files from Oracle eDelivery, unzipped them, used the ImageCreator to build the network installation image (whatever that means), then proceeded to install Siebel Tools and Web Client.

All of this on Windows Vista, mind you. I had to run the installs as Administrator in Windows XP SP2 compatibility mode, otherwise, Oracle's Universal Install just disappeared as soon as files started copying.

Well, the Tools installation completed, but failed to start. The Web Client just plain didn't install. The Web Client installation failed just after checking the system prerequisites, before choosing language packs. When I attempted to continue, ignoring the error, no language packs appeared for selection.

A little searching around brought me to this Oracle doc: "Siebel System Requirement and Supported Platforms, Version 8.1 Rev. A, January 2009"

"Table 7. Software Requirements for the Siebel Developer Web Client" lists Microsoft Windows XP Professional SP2+ as required software. Does this mean that Siebel's latest release cannot run on Windows Vista? I hope not.

I hope that it's just something I did wrong, because it would be disappointing to learn that the Windows 7 Beta has already been released, and Siebel still hasn't found a way to get their Tools and Mobile Client to run on Windows Vista.

If anyone knows how to get Siebel 8.1.1.0 running on Windows Vista, drop me a line, and I'll update this posting. Meanwhile, I'll be playing with 8.1.1.0 on my other machine which is running Windows XP SP2. And FYI, it's running just fine on that.

Thursday, February 5, 2009

eScript Date Parser Bug

The Siebel eScript date parser does not correctly recognize dates in the 12AM hour in the format HH:MM:SS AM. This is a bug in some versions of the eScript engine, affecting server scripts, but not browser scripts - since browser scripts use the web browser's javascript engine. Check out the example below.

Siebel eScript (7.7.2.6 SIA [18372]):
// incorrectly evaluates to noon
// "Sat Oct 18 12:00:00 2008"
new Date ('10/18/2008 12:00:00 AM").toString ();
JavaScript engine from IE 6.0.2900.2180.xpsp_sp2_gdr.070227-2254:
// correctly evaluates to midnight
// "Sat Oct 18 00:00:00 PDT 2008 12:00:00 AM"
new Date ('10/18/2008 12:00:00 AM").toString ();
We just got a quickfix from Oracle for this nasty little bug we found, but the fix is on our private branch. So if this is affecting you, you might want to request it too. It took a few months between logging the SR and getting the patch delivered, so we had to work around the problem manually. We created a function to turn a string into a date object. In case you need it, here it is:
function StringToDate (sDateString)
{ // takes a date string, parses it using the
  // escript date parser, and returns it as a
  // date object. also works around a known
  // defect with the escript date parser
  // (siebel sr 3-765130491)

  var dt = new Date (sDateString);

  // address siebel product defect where
  // 12:XX AM dates get translated to 12:XX PM
  // in the date constructor
  if (/^[^:]*12(:\d{1,2}){0,2}\s*[Aa][Mm]/.test (sDateString))
  {
    // the time appears to be 12:XX AM,
    // so verify it
    if (dt.getHours () == 12)
    {
      // the hours are off, so fix it
      dt.setHours (0);
    }
  }

  return (dt);
}
I wonder if this problem occurs with date formats from other locales... In case you didn't know, I'm in the US.

Tuesday, February 3, 2009

Working with the Local Database

Us developers all have local databases we use to make changes before checking them into the server, right? "No, I develop directly on the server," is not the right answer. Sometimes it's useful to work directly with the local database to view or change data. Here are some basics to get your started.

Logging In

Siebel ships with a dandy SQL client you can use to log into your local database interactively. The program is located in both the "..\tools\bin" and "..\web client\bin" directories, called dbisqlc.exe.

Start this program and you'll enter directly onto a "Login" tab where you can fill in your username and password. In the "Database" tab, specify the "Database file", pointing to the local database to which you want to connect.

Once connected, running SQL statements is pretty self-explanatory.

Logging in as SIEBEL

To make schema changes, log in as SIEBEL, using the same password as when you log in as yourself. By default, the passwords are set to the same value when you get your extract.

Dropping a Column

After you delete a column from a table in the Siebel Repository, applying that change will not drop the column in your database. You'll have to drop it manually using this command:
alter table <table name> drop <column name>
Notice that there is no "column" keyword before the column name.

Unlocking Projects

When you get a locked project from the server, its copy remains locked on your local database as well. If you want to locally test some changes in that project, you're going to have to forcibly unlock it using this SQL update statement:
update s_project set locked_flg = 'N' where name = <project name>
Changing your Password

Passwords are set when you extract the database, but can be changed using this command:
grant connect to <username> identified by <password>
If you change your own password, don't forget to set SIEBEL's password to the same; otherwise you might forget it. You'll have to log in as SIEBEL to change SIEBEL's password. SIEBEL can also change your password, so you might as well log in as him.

Friday, January 30, 2009

Getting the Call Stack from eScript

I've been working on some routines for exception logging; basically recording some important data when an exception condition occurs. Right now it records things like the error code, error message, application name, active view name, user id, primary position id, organization id, server name, and a bunch of other stuff. You get the idea.

Well today, I decided to add a call stack for those times the exception occurs in server script.

Where did I get the call stack from? I noticed that the call stack is displayed when an exception is thrown and we push it to the screen using TheApplication().RaiseErrorText(e), where e is the exception object. So I knew the call stack was available somewhere in that exception object.

Let's hack the exception object and see what it contains. A quick run of this code in the business service simulator was very revealing:
try
{
  TheApplication ().RaiseErrorText ("Hello, World!");
}
catch (e)
{
  for (var key in e)
    Outputs.SetProperty (key, e[key]);
}
Here are the values that came out:
PropertyValue
errTextHello, World!
errCode22709
nameError
message SiebelError: Hello, World!***RAISE ERROR TEXT*** Error near no filename:159 [RaiseErrorText()]. from no filename:159 [Service_PreInvokeMethod()]

So I can see that the message property of the exception object houses the message I threw along with the call stack (at least when I threw that exception using RaiseErrorText()). So I can use that to my advantage.

Actually, the construct was something like this:
"SiebelError:" + <error message> + "***RAISE ERROR TEXT***" + <call stack>
I didn't really have a need for anything before the call stack, so I stripped out everything up to and including the ***RAISE ERROR TEXT*** bit. So finally, here's the function I wrote to get the call stack.
function GetCallStack ()
{
  try
  {
    TheApplication ().RaiseErrorText ("");
  }
  catch (e)
  {
    return (e.message.replace (/^.*\*{3}RAISE ERROR TEXT\*{3}/, "");
  }
}
If that return line looks Chinese to you it's because I am part Chinese. Allow me to translate...

I stripped out the junk prefix by using the string.replace() method to replace unwanted text with an empty string. The unwanted text was expressed as a regular expression, which basically reads, "Everything from the beginning of the line until ***RAISE ERROR TEXT***" So pretty straightforward logically.

Well there you have it! I'll save the primer on regular expressions for another day.