Custom eScript Engine

This long overdue article, is a continuation of one of my earliest articles at Impossible Siebel, how to run eScript on a Siebel server without compiling.

The ability to write eScript, and test it without compiling, was introduced in Siebel 8 with "Fix and Go". Convenient as it sounds, Fix and Go, isn't the ultimate solution.

Even though you don't have to compile, it is still necessary to launch a debugging session from a thick client to test the code. To unit test a stand alone business service, it is more appropriate to use Business Service simulator, which allows the developer to create client side code, and test it it on the fly. Business Service Simulator is useful, but using it, is like using Notepad to write code, it suffices but isn't user friendly. Business Service Simulator by its own nature cannot test UI context, It is also quite clunky, for testing small snippets of code, which is where the original article took a point, providing the developer with an alternative to write eScript on the fly in their favourite text editor, and test it without compiling.

This article is a walk through for the advanced reader, who wants run through the implementation process to build such a tool. I'll also provide the full source code to the translation class, that makes it possible for us to emulate the Siebel eScript API.

Pre-requisites

The following will seem familiar if you've gone through my article on a creating a Java Business Service.

1. Java for Dummies

Learn some Java

2. Sun Java Software Development Kit

Goto the sun website, download and install the SDK

http://java.sun.com/javase/downloads/index.jsp

3. Java IDE

Download and install your favourite Java IDE.

4. Siebel Enterprise

This method will only allow you to run eScript on the server, and not on a local machine, so we need a Siebel Server installation to connect to.

Implementation

1. Connect to Siebel using the JDB Interface

Ensure you have read the following article on Support Web, this will provide the foundations for our eScript program.

How To Use Siebel Java Data Bean [ID 476902.1]

I've run through the instructions again and started from scratch, to re-build this program for this article, and found the Siebel documentation to be very good.

Once you have followed the instructions on this document, you should have a working Java Data Bean (JDB) connection to your Siebel server.

2. Import ECMA scripting engine

private ScriptEngineManager oScptMgr    = new ScriptEngineManager();
private ScriptEngine        jsEngine    = oScptMgr.getEngineByName("ECMAScript");//Instantiate base engine


Siebel is based on the ECMA standard, so we are importing the ECMAScript engine to provide us with the base language, to which we will add the eScript translation layer.

3. Pass Siebel Java application object into ECMA engine

jsEngine.put("oApp",m_dataBean);
//oApp is the Siebel Java Application and we want to load this into the ECMA engine


We need to pass the Siebel application reference, that we have instantiated earlier following the Siebel tutorial, into the ECMA engine instance that we created above.

This statement creates a global object inside our ECMAEngine called oApp that references the m_dataBean handle in our Java program. This allows our ECMA engine to access all the methods available in the JDB Interface, using a scripting language.

Nice! now we have an ECMA language with the Siebel JDB API as a global.

4. Create a class abstraction layer to translate the eScript language into JDB methods.

This is the custom eScript API, that I built several years ago, but never got around to publishing. It is provided as is, with no warranties, or support, and use of it, is at your own risk.
                       
/**************************************************************************************************************
* Component Name        :  Impossible Siebel eScript Translation Class
* Component Information :  Provides a wrapper for the Siebel JDB methods
* Date  Author
* 2009  Jason Le 
* ============================================================================================================
* 01MAR09 JLE  Initial version
**************************************************************************************************************/
//Define Escript Constants
var ContinueOperation = true;       var CancelOperation       = false;

//ExecuteQuery constants
var ForwardOnly       = true;       var ForwardBackward       = false;
var NewAfter          = true;       var NewBefore             = false;

//SetViewMode
var SalesRepView      = 0;          var OrganizationView      = 5;
var ManagerView       = 1;          var GroupView             = 7;
var PersonalView      = 2;          var CatalogView           = 8;
var AllView           = 3;          var SubOrganizationView   = 9;

var Inputs            = oApp.newPropertySet();
var Outputs           = oApp.newPropertySet();
var arrDefaultInput   = [];
var Appinstance       = 0;
var gApplication;

//TheApplication() Class
function TheApplication() {
  if (gApplication == null) {
    Appinstance = Appinstance+1;
    gApplication = new class_app(oApp);
  }
  return gApplication;
}

//class_app.prototype = oApp;
class_app.prototype.GetProfileAttr          = class_app_GetProfileAttr;
class_app.prototype.SetProfileAttr          = class_app_SetProfileAttr;
class_app.prototype.Trace                   = class_app_Trace;
class_app.prototype.TraceOff                = class_app_TraceOff;
class_app.prototype.TraceOn                 = class_app_TraceOn;
class_app.prototype.PositionId              = class_app_PositionId;
class_app.prototype.PositionName            = class_app_PositionName;
class_app.prototype.LoginId                 = class_app_LoginId;
class_app.prototype.LoginName               = class_app_LoginName;
class_app.prototype.InvokeMethod            = class_app_InvokeMethod;
class_app.prototype.GetBusObject            = GetBusObject;
class_app.prototype.GetService              = GetService;
class_app.prototype.NewPropertySet          = NewPropertySet;

function class_app(application)             {if(application != null){this._application = application; }}
function class_app_GetProfileAttr(v)        {return this._application.getProfileAttr(v) ;} //+ '';}
function class_app_SetProfileAttr(n,v)      {return this._application.setProfileAttr(n,v);}
function class_app_Trace(v)                 {trace(v);}
function class_app_TraceOff()               {return this._application.traceOff();}
function class_app_TraceOn(f,t,s)           {return this._application.traceOn(f,t,s);}
function class_app_PositionId()             {return this._application.positionId() ;} //+ '';}
function class_app_PositionName()           {return this._application.positionName() ;} //+ '';}
function class_app_LoginId()                {return this._application.loginId() ;} //+ '';}
function class_app_LoginName()              {return this._application.loginName() ;} //+ '';}
function class_app_NewPropertySet()         {return this._application.newPropertySet();}
function class_app_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._application.invokeMethod(sMethod, aInput);
}



//NewPropertySet() Class
//class_PS.prototype = new class_PS(oPS);
class_PS.prototype.PropertyExists           = class_PS_PropertyExists;
class_PS.prototype.GetProperty              = class_PS_GetProperty;
class_PS.prototype.getProperty              = class_PS_GetProperty;
class_PS.prototype.SetProperty              = class_PS_SetProperty;
class_PS.prototype.setProperty              = class_PS_SetProperty;
class_PS.prototype.GetValue                 = class_PS_GetValue;
class_PS.prototype.SetValue                 = class_PS_SetValue;
class_PS.prototype.GetType                  = class_PS_GetType;
class_PS.prototype.SetType                  = class_PS_SetType;
class_PS.prototype.Copy                     = class_PS_Copy;
class_PS.prototype.Reset                    = class_PS_Reset;
class_PS.prototype.GetChild                 = class_PS_GetChild;
class_PS.prototype.AddChild                 = class_PS_AddChild;
class_PS.prototype.RemoveChild              = class_PS_RemoveChild;
class_PS.prototype.RemoveProperty           = class_PS_RemoveProperty;
class_PS.prototype.InsertChildAt            = class_PS_InsertChildAt;
class_PS.prototype.GetFirstProperty         = class_PS_GetFirstProperty;
class_PS.prototype.GetNextProperty          = class_PS_GetNextProperty;
class_PS.prototype.GetPropertyCount         = class_PS_GetPropertyCount;

function NewPropertySet()                  {return new class_PS(this._application.newPropertySet() );}
//function NewPropertySet()                   {return new class_PS( oPS );}
function class_PS(PS)                       {if(PS != null){this._PS = PS;} }
function class_PS_PropertyExists(v)         {return this._PS.propertyExists(v);}
function class_PS_GetProperty(v)            {return this._PS.getProperty(v) ;} //+ '';}
function class_PS_SetProperty(n,v)          {return this._PS.setProperty(n,v);}
function class_PS_GetValue(v)               {return this._PS.getValue(v) ;} //+ '';}
function class_PS_SetValue(v)               {return this._PS.setValue(v);}
function class_PS_GetType(v)                {return this._PS.getType(v) ;} //+ '';}
function class_PS_SetType(v)                {return this._PS.setType(v);}
function class_PS_Copy()                    {return this._PS.copy();}
function class_PS_Reset()                   {return this._PS.reset();}
function class_PS_GetChild(i)               {return this._PS.getChild(i);}
function class_PS_AddChild(Ps)              {return this._PS.addChild(Ps);}
function class_PS_RemoveChild(i)            {return this._PS.removeChild(i);}
function class_PS_RemoveProperty(v)         {return this._PS.removeProperty(v);}
function class_PS_InsertChildAt(obj,i)      {return this._PS.insertChildAt(obj,i);}
function class_PS_GetFirstProperty()        {return this._PS.getFirstProperty() ;} //+ '';}
function class_PS_GetNextProperty()         {return this._PS.getNextProperty() ;} //+ '';}
function class_PS_GetPropertyCount()        {return this._PS.getPropertyCount();}



//GetBusObject() Class
class_BO.prototype.Name                     = class_BO_Name;
class_BO.prototype.GetBusComp               = GetBusComp;

function GetBusObject(BOName)               {return new class_BO(this._application.getBusObject(BOName));}
function class_BO(oBO)                      {if(oBO != null){this._BO = oBO; } }
function class_BO_Name()                    {return this._BO.name();}

//GetService() Class
class_Svc.prototype.Name                    = class_Svc_Name;
class_Svc.prototype.InvokeMethod            = class_Svc_InvokeMethod;
class_Svc.prototype.PropertyExists          = class_Svc_PropertyExists;
class_Svc.prototype.GetProperty             = class_Svc_GetProperty;
class_Svc.prototype.SetProperty             = class_Svc_SetProperty;

function GetService(SvcName)                {return new class_Svc(this._application.getService(SvcName));}
function class_Svc(oSvc)                    {if(oSvc != null){this._Svc = oSvc; } }
function class_Svc_Name()                   {return this._Svc.getName() ;} //+ '';}
function class_Svc_InvokeMethod(Meth,Ps1,Ps2){
  return this._Svc.invokeMethod(Meth,Ps1,Ps2);
}
function class_Svc_PropertyExists(v)        {return this._Svc.propertyExists(v);}
function class_Svc_GetProperty(v)           {return this._Svc.getProperty(v) ;} //+ '';}
function class_Svc_SetProperty(n,v)         {return this._Svc.setProperty(n,v);}


//GetBusComp() Class
class_BC.prototype.Name                     = class_BC_Name;
class_BC.prototype.ActivateField            = class_BC_ActivateField;
class_BC.prototype.ActivateMultipleFields   = class_BC_ActivateMultipleFields ;
class_BC.prototype.Associate                = class_BC_Associate;
class_BC.prototype.ClearToQuery             = class_BC_ClearToQuery;
class_BC.prototype.CountRecords             = class_BC_CountRecords;
class_BC.prototype.DeactivateFields         = class_BC_DeactivateFields;
class_BC.prototype.DeleteRecord             = class_BC_DeleteRecord;
class_BC.prototype.ExecuteQuery             = class_BC_ExecuteQuery;
class_BC.prototype.FirstRecord              = class_BC_FirstRecord;
//class_BC.prototype.GetAssocBusComp          = class_BC_GetAssocBusComp;
class_BC.prototype.GetFieldValue            = class_BC_GetFieldValue;
class_BC.prototype.GetFormattedFieldValue   = class_BC_GetFormattedFieldValue;
class_BC.prototype.GetMVGBusComp            = GetMVGBusComp;//class_BC_GetMVGBusComp;
class_BC.prototype.GetNamedSearch           = class_BC_GetNamedSearch;
class_BC.prototype.GetPicklistBusComp       = class_BC_GetPicklistBusComp;
class_BC.prototype.GetSearchExpr            = class_BC_GetSearchExpr;
class_BC.prototype.GetSearchSpec            = class_BC_GetSearchSpec;
class_BC.prototype.GetViewMode              = class_BC_GetViewMode;
class_BC.prototype.InvokeMethod             = class_BC_InvokeMethod;
class_BC.prototype.LastRecord               = class_BC_LastRecord;
class_BC.prototype.NewRecord                = class_BC_NewRecord;
class_BC.prototype.NextRecord               = class_BC_NextRecord;
class_BC.prototype.ParentBusComp            = class_BC_ParentBusComp;
class_BC.prototype.Pick                     = class_BC_Pick;
class_BC.prototype.PreviousRecord           = class_BC_PreviousRecord;
class_BC.prototype.RefineQuery              = class_BC_RefineQuery;
class_BC.prototype.SearchExpr               = class_BC_SearchExpr;
class_BC.prototype.SetFieldValue            = class_BC_SetFieldValue;
class_BC.prototype.SetFormattedFieldValue   = class_BC_SetFormattedFieldValue;
class_BC.prototype.SetSearchSpec            = class_BC_SetSearchSpec;
class_BC.prototype.SetSearchExpr            = class_BC_SetSearchExpr;
class_BC.prototype.SetSortSpec              = class_BC_SetSortSpec;
class_BC.prototype.SetViewMode              = class_BC_SetViewMode;
class_BC.prototype.UndoRecord               = class_BC_UndoRecord;
class_BC.prototype.ViewMode                 = class_BC_ViewMode;
class_BC.prototype.WriteRecord              = class_BC_WriteRecord;

function GetBusComp(BCName)                 {return new class_BC(this._BO.getBusComp(BCName));}
function class_BC(oBC)                      {if(oBC != null){this._BC = oBC; } }
function class_BC_Name()                    {return this._BC.name();}
function class_BC_ActivateField(v)          {return this._BC.activateField(v);}
function class_BC_ActivateMultipleFields(Ps){return this._BC.activateMultipleFields(Ps);}
function class_BC_Associate(i)              {return this._BC.associate(i);}
function class_BC_ClearToQuery()            {return this._BC.clearToQuery();}
function class_BC_CountRecords(){
  var oMVG = this._BC;
  var icount = 0;
  with (oMVG){
    executeQuery(ForwardBackward);

    var isRecord = firstRecord();
    if (isRecord)
    {
      while (isRecord)
      {
      icount++;
      isRecord = nextRecord();
      }
    }
    firstRecord();
  }
  
  return icount;
}
function class_BC_DeactivateFields()        {return this._BC.deactivateFields();}
function class_BC_DeleteRecord()            {return this._BC.deleteRecord();}
function class_BC_ExecuteQuery(v)           {return this._BC.executeQuery(v);}
function class_BC_FirstRecord()             {return this._BC.firstRecord();}
function class_BC_NextRecord()              {return this._BC.nextRecord();}
//function class_BC_GetAssocBusComp()         {return this._BC.getAssocBusComp();}
function class_BC_GetFieldValue(v)          {return this._BC.getFieldValue(v) + '';} //+ '';}
function class_BC_GetFormattedFieldValue(v) {return this._BC.getFormattedFieldValue(v) ;} //+ '';}
function class_BC_GetMVGBusComp(v)          {return this._BC.getMVGBusComp(v);}
function class_BC_GetNamedSearch(v)         {return this._BC.getNamedSearch(v);}
function class_BC_GetPicklistBusComp(v)     {return this._BC.getPicklistBusComp(v);}
function class_BC_GetSearchExpr()           {return this._BC.getSearchExpr() ;} //+ '';}
function class_BC_GetSearchSpec()           {return this._BC.getSearchSpec() ;} //+ '';}
function class_BC_GetViewMode()             {return this._BC.getViewMode() ;} //+ '';}
function class_BC_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._BC.invokeMethod(sMethod, aInput);
}
function class_BC_LastRecord()              {return this._BC.lastRecord();}
function class_BC_NewRecord(v)              {return this._BC.newRecord(v);}
//function class_BC_NextRecord()              {return this._BC.nextRecord();}
function class_BC_ParentBusComp()           {return this._BC.parentBusComp();}
function class_BC_Pick()                    {return this._BC.pick();}
function class_BC_PreviousRecord()          {return this._BC.previousRecord();}
function class_BC_RefineQuery()             {return this._BC.refineQuery();}
function class_BC_SearchExpr(v)             {return this._BC.searchExpr(v);}
function class_BC_SetFieldValue(n,v)        {return this._BC.setFieldValue(n,v);}
function class_BC_SetFormattedFieldValue(n,v)        {return this._BC.setFormattedFieldValue(n,v);}
function class_BC_SetSearchExpr(v)          {return this._BC.setSearchExpr(v);}
function class_BC_SetSearchSpec(n,v)          {return this._BC.setSearchSpec(n,v);}
function class_BC_SetSortSpec(v)          {return this._BC.setSortSpec(v);}
function class_BC_SetViewMode(v)            {return this._BC.setViewMode(v);}
function class_BC_UndoRecord()              {return this._BC.undoRecord();}
function class_BC_ViewMode(v)               {return this._BC.viewMode(v);}
function class_BC_WriteRecord()             {return this._BC.writeRecord();}

//GetMVGBusComp() Class
class_MvgBC.prototype.Name                     = class_MvgBC_Name;
class_MvgBC.prototype.ActivateField            = class_MvgBC_ActivateField;
class_MvgBC.prototype.ActivateMultipleFields   = class_MvgBC_ActivateMultipleFields ;
class_MvgBC.prototype.Associate                = class_MvgBC_Associate;
class_MvgBC.prototype.ClearToQuery             = class_MvgBC_ClearToQuery;
class_MvgBC.prototype.CountRecords             = class_MvgBC_CountRecords;
class_MvgBC.prototype.DeactivateFields         = class_MvgBC_DeactivateFields;
class_MvgBC.prototype.DeleteRecord             = class_MvgBC_DeleteRecord;
class_MvgBC.prototype.ExecuteQuery             = class_MvgBC_ExecuteQuery;
class_MvgBC.prototype.FirstRecord              = class_MvgBC_FirstRecord;
//class_MvgBC.prototype.GetAssocBusComp          = class_MvgBC_GetAssocBusComp;
class_MvgBC.prototype.GetFieldValue            = class_MvgBC_GetFieldValue;
class_MvgBC.prototype.GetFormattedFieldValue   = class_MvgBC_GetFormattedFieldValue;
class_MvgBC.prototype.GetMVGBusComp            = class_MvgBC_GetMVGBusComp;
class_MvgBC.prototype.GetAssocBusComp          = GetAssocBusComp;
class_MvgBC.prototype.GetNamedSearch           = class_MvgBC_GetNamedSearch;
class_MvgBC.prototype.GetPicklistBusComp       = class_MvgBC_GetPicklistBusComp;
class_MvgBC.prototype.GetSearchExpr            = class_MvgBC_GetSearchExpr;
class_MvgBC.prototype.GetSearchSpec            = class_MvgBC_GetSearchSpec;
class_MvgBC.prototype.GetViewMode              = class_MvgBC_GetViewMode;
class_MvgBC.prototype.InvokeMethod             = class_MvgBC_InvokeMethod;
class_MvgBC.prototype.LastRecord               = class_MvgBC_LastRecord;
class_MvgBC.prototype.NewRecord                = class_MvgBC_NewRecord;
class_MvgBC.prototype.NextRecord               = class_MvgBC_NextRecord;
class_MvgBC.prototype.ParentBusComp            = class_MvgBC_ParentBusComp;
class_MvgBC.prototype.Pick                     = class_MvgBC_Pick;
class_MvgBC.prototype.PreviousRecord           = class_MvgBC_PreviousRecord;
class_MvgBC.prototype.RefineQuery              = class_MvgBC_RefineQuery;
class_MvgBC.prototype.SearchExpr               = class_MvgBC_SearchExpr;
class_MvgBC.prototype.SetFieldValue            = class_MvgBC_SetFieldValue;
class_MvgBC.prototype.SetFormattedFieldValue   = class_MvgBC_SetFormattedFieldValue;
class_MvgBC.prototype.SetSearchSpec            = class_MvgBC_SetSearchSpec;
class_MvgBC.prototype.SetSearchExpr            = class_MvgBC_SetSearchExpr;
class_MvgBC.prototype.SetSortSpec              = class_MvgBC_SetSortSpec;
class_MvgBC.prototype.SetViewMode              = class_MvgBC_SetViewMode;
class_MvgBC.prototype.UndoRecord               = class_MvgBC_UndoRecord;
class_MvgBC.prototype.ViewMode                 = class_MvgBC_ViewMode;
class_MvgBC.prototype.WriteRecord              = class_MvgBC_WriteRecord;

function GetMVGBusComp(FieldName)              {return new class_MvgBC(this._BC.getMVGBusComp(FieldName));}
function class_MvgBC(oMvgBC)                   {if(oMvgBC != null){this._MvgBC = oMvgBC; } }
function class_MvgBC_Name()                    {return this._MvgBC.name();}
function class_MvgBC_ActivateField(v)          {return this._MvgBC.activateField(v);}
function class_MvgBC_ActivateMultipleFields(Ps){return this._MvgBC.activateMultipleFields(Ps);}
function class_MvgBC_Associate(i)              {return this._MvgBC.associate(i);}
function class_MvgBC_ClearToQuery()            {return this._MvgBC.clearToQuery();}
//function class_MvgBC_CountRecords()            {return this._MvgBC.countRecords();}
function class_MvgBC_CountRecords(){
  var oMVG = this._MvgBC;
  var icount = 0;
  with (oMVG){
    executeQuery(ForwardBackward);

    var isRecord = firstRecord();
    if (isRecord)
    {
      while (isRecord)
      {
      icount++;
      isRecord = nextRecord();
      }
    }
    firstRecord();
  }
  
  return icount;
}
function class_MvgBC_DeactivateFields()        {return this._MvgBC.deactivateFields();}
function class_MvgBC_DeleteRecord()            {return this._MvgBC.deleteRecord();}
function class_MvgBC_ExecuteQuery(v)           {return this._MvgBC.executeQuery(v);}
function class_MvgBC_FirstRecord()             {return this._MvgBC.firstRecord();}
function class_MvgBC_NextRecord()              {return this._MvgBC.nextRecord();}
//function class_MvgBC_GetAssocBusComp()         {return this._MvgBC.getAssocBusComp();}
function class_MvgBC_GetFieldValue(v)          {return this._MvgBC.getFieldValue(v) ;} //+ '';}
function class_MvgBC_GetFormattedFieldValue(v) {return this._MvgBC.getFormattedFieldValue(v) ;} //+ '';}
function class_MvgBC_GetMVGBusComp(v)          {return this._MvgBC.getMVGBusComp(v);}
function class_MvgBC_GetNamedSearch(v)         {return this._MvgBC.getNamedSearch(v) ;} //+ '';}
function class_MvgBC_GetPicklistBusComp(v)     {return this._MvgBC.getPicklistBusComp(v);}
function class_MvgBC_GetSearchExpr()           {return this._MvgBC.getSearchExpr() ;} //+ '';}
function class_MvgBC_GetSearchSpec()           {return this._MvgBC.getSearchSpec() ;} //+ '';}
function class_MvgBC_GetViewMode()             {return this._MvgBC.getViewMode() ;} //+ '';}
function class_MvgBC_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._MvgBC.invokeMethod(sMethod, aInput);
}
function class_MvgBC_LastRecord()              {return this._MvgBC.lastRecord();}
function class_MvgBC_NewRecord(v)              {return this._MvgBC.newRecord(v);}
function class_MvgBC_NextRecord()              {return this._MvgBC.nextRecord();}
function class_MvgBC_ParentBusComp()           {return this._MvgBC.parentBusComp();}
function class_MvgBC_Pick()                    {return this._MvgBC.pick();}
function class_MvgBC_PreviousRecord()          {return this._MvgBC.previousRecord();}
function class_MvgBC_RefineQuery()             {return this._MvgBC.refineQuery();}
function class_MvgBC_SearchExpr(v)             {return this._MvgBC.searchExpr(v);}
function class_MvgBC_SetFieldValue(n,v)        {return this._MvgBC.setFieldValue(n,v);}
function class_MvgBC_SetFormattedFieldValue(n,v)        {return this._MvgBC.setFormattedFieldValue(n,v);}
function class_MvgBC_SetSearchExpr(v)          {return this._MvgBC.setSearchExpr(v);}
function class_MvgBC_SetSearchSpec(n,v)        {return this._MvgBC.setSearchSpec(n,v);}
function class_MvgBC_SetSortSpec(v)            {return this._MvgBC.setSortExpr(v);}
function class_MvgBC_SetViewMode(v)            {return this._MvgBC.setViewMode(v);}
function class_MvgBC_UndoRecord()              {return this._MvgBC.undoRecord();}
function class_MvgBC_ViewMode(v)               {return this._MvgBC.viewMode(v);}
function class_MvgBC_WriteRecord()             {return this._MvgBC.writeRecord();}

//GetAssocBusComp() Class
class_AssBC.prototype.Name                     = class_AssBC_Name;
class_AssBC.prototype.ActivateField            = class_AssBC_ActivateField;
class_AssBC.prototype.ActivateMultipleFields   = class_AssBC_ActivateMultipleFields ;
class_AssBC.prototype.Associate                = class_AssBC_Associate;
class_AssBC.prototype.ClearToQuery             = class_AssBC_ClearToQuery;
class_AssBC.prototype.CountRecords             = class_AssBC_CountRecords;
class_AssBC.prototype.DeactivateFields         = class_AssBC_DeactivateFields;
class_AssBC.prototype.DeleteRecord            = class_AssBC_DeleteRecord;
class_AssBC.prototype.ExecuteQuery             = class_AssBC_ExecuteQuery;
class_AssBC.prototype.FirstRecord              = class_AssBC_FirstRecord;
//class_AssBC.prototype.GetAssocBusComp          = class_AssBC_GetAssocBusComp;
class_AssBC.prototype.GetFieldValue            = class_AssBC_GetFieldValue;
class_AssBC.prototype.GetFormattedFieldValue   = class_AssBC_GetFormattedFieldValue;
//class_AssBC.prototype.GetAssocBusComp          = class_AssBC_GetAssocBusComp;
class_AssBC.prototype.GetNamedSearch           = class_AssBC_GetNamedSearch;
class_AssBC.prototype.GetPicklistBusComp       = class_AssBC_GetPicklistBusComp;
class_AssBC.prototype.GetSearchExpr            = class_AssBC_GetSearchExpr;
class_AssBC.prototype.GetSearchSpec            = class_AssBC_GetSearchSpec;
class_AssBC.prototype.GetViewMode              = class_AssBC_GetViewMode;
class_AssBC.prototype.InvokeMethod             = class_AssBC_InvokeMethod;
class_AssBC.prototype.LastRecord               = class_AssBC_LastRecord;
class_AssBC.prototype.NewRecord                = class_AssBC_NewRecord;
class_AssBC.prototype.NextRecord               = class_AssBC_NextRecord;
class_AssBC.prototype.ParentBusComp            = class_AssBC_ParentBusComp;
class_AssBC.prototype.Pick                     = class_AssBC_Pick;
class_AssBC.prototype.PreviousRecord           = class_AssBC_PreviousRecord;
class_AssBC.prototype.RefineQuery              = class_AssBC_RefineQuery;
class_AssBC.prototype.SearchExpr               = class_AssBC_SearchExpr;
class_AssBC.prototype.SetFieldValue            = class_AssBC_SetFieldValue;
class_AssBC.prototype.SetFormattedFieldValue   = class_AssBC_SetFormattedFieldValue;
class_AssBC.prototype.SetSearchSpec            = class_AssBC_SetSearchSpec;
class_AssBC.prototype.SetSearchExpr            = class_AssBC_SetSearchExpr;
class_AssBC.prototype.SetSortSpec              = class_AssBC_SetSortSpec;
class_AssBC.prototype.SetViewMode              = class_AssBC_SetViewMode;
class_AssBC.prototype.UndoRecord               = class_AssBC_UndoRecord;
class_AssBC.prototype.ViewMode                 = class_AssBC_ViewMode;
class_AssBC.prototype.WriteRecord              = class_AssBC_WriteRecord;

function GetAssocBusComp()                {return new class_AssBC(this._MvgBC.getAssocBusComp());}
function class_AssBC(oAssBC)                   {if(oAssBC != null){this._AssBC = oAssBC; } }
function class_AssBC_Name()                    {return this._AssBC.name();}
function class_AssBC_ActivateField(v)          {return this._AssBC.activateField(v);}
function class_AssBC_ActivateMultipleFields(Ps){return this._AssBC.activateMultipleFields(Ps);}
function class_AssBC_Associate(i)              {return this._AssBC.associate(i);}
function class_AssBC_ClearToQuery()            {return this._AssBC.clearToQuery();}
function class_AssBC_CountRecords(){
  var oMVG = this._AssBC;
  var icount = 0;
  with (oMVG){
    executeQuery(ForwardBackward);

    var isRecord = firstRecord();
    if (isRecord)
    {
      while (isRecord)
      {
      icount++;
      isRecord = nextRecord();
      }
    }
    firstRecord();
  }
  
  return icount;
}
function class_AssBC_DeactivateFields()        {return this._AssBC.deactivateFields();}
function class_AssBC_DeleteRecord()            {return this._AssBC.deleteRecord();}
function class_AssBC_ExecuteQuery(v)           {return this._AssBC.executeQuery(v);}
function class_AssBC_FirstRecord()             {return this._AssBC.firstRecord();}
function class_AssBC_NextRecord()              {return this._AssBC.nextRecord();}
function class_AssBC_GetAssocBusComp()         {return this._AssBC.getAssocBusComp();}
function class_AssBC_GetFieldValue(v)          {return this._AssBC.getFieldValue(v) ;} //+ '';}
function class_AssBC_GetFormattedFieldValue(v) {return this._AssBC.getFormattedFieldValue(v) ;} //+ '';}
//function class_AssBC_GetAssocBusComp(v)          {return this._AssBC.GetAssocBusComp(v);}
function class_AssBC_GetNamedSearch(v)         {return this._AssBC.getNamedSearch(v) ;} //+ '';}
function class_AssBC_GetPicklistBusComp(v)     {return this._AssBC.getPicklistBusComp(v);}
function class_AssBC_GetSearchExpr()           {return this._AssBC.getSearchExpr() ;} //+ '';}
function class_AssBC_GetSearchSpec()           {return this._AssBC.getSearchSpec() ;} //+ '';}
function class_AssBC_GetViewMode()             {return this._AssBC.getViewMode() ;} //+ '';}
function class_AssBC_InvokeMethod(){
var sMethod = arguments[0];
var aInput  = [];
  for(i=1; i<arguments.length; i++){
    aInput.push(arguments[i])
  }
  return this._AssBC.invokeMethod(sMethod, aInput);
}
function class_AssBC_LastRecord()              {return this._AssBC.lastRecord();}
function class_AssBC_NewRecord(v)              {return this._AssBC.newRecord(v);}
function class_AssBC_NextRecord()              {return this._AssBC.nextRecord();}
function class_AssBC_ParentBusComp()           {return this._AssBC.parentBusComp();}
function class_AssBC_Pick()                    {return this._AssBC.pick();}
function class_AssBC_PreviousRecord()          {return this._AssBC.previousRecord();}
function class_AssBC_RefineQuery()             {return this._AssBC.refineQuery();}
function class_AssBC_SearchExpr(v)             {return this._AssBC.searchExpr(v);}
function class_AssBC_SetFieldValue(n,v)        {return this._AssBC.setFieldValue(n,v);}
function class_AssBC_SetFormattedFieldValue(n,v)        {return this._AssBC.setFormattedFieldValue(n,v);}
function class_AssBC_SetSearchExpr(v)          {return this._AssBC.setSearchExpr(v);}
function class_AssBC_SetSearchSpec(n,v)        {return this._AssBC.setSearchSpec(n,v);}
function class_AssBC_SetSortSpec(v)            {return this._AssBC.setSortSpec(v);}
function class_AssBC_SetViewMode(v)            {return this._AssBC.setViewMode(v);}
function class_AssBC_UndoRecord()              {return this._AssBC.undoRecord();}
function class_AssBC_ViewMode(v)               {return this._AssBC.viewMode(v);}
function class_AssBC_WriteRecord()             {return this._AssBC.writeRecord();}

function trace(v){
    oSysOut.println(v); 
}

Copy this into a file called "Impossible_eScript.js", which we will read, and load into our ECMA engine.
sEscriptToRun = readFile("\\Your path\\Impossible_eScript.js" );
jsEngine.eval(sEscriptToRun);//Load eScript class


The above statement imports a custom eScript like API into the ECMA engine. This is required because the JDB interface is slightly different from eScript syntax. This translation class converts eScript statements into calls that the JDB can understand.

Eg. eScript syntax is converted into Java style methods with initial lower case method names

TheApplication().NewPropertySet();//eScript
TheApplication().newPropertySet();//JDB Interface


The eScript translation class will create an abstraction layer that allows us to use conventional eScript against the JDB instance, it also implements some missing methods in the standard JDB API.

5. Run your eScript on the fly against the Server.

The pieces are comming together now, we have an ECMA engine, a Siebel Application instance, and an eScript API.

All we need to do is read in our eScript command file, which we can edit from any text editor, and run the code against the JDB connection.

String sEscriptToRun = readFile("\\Your path\\Your_eScript_commands.js");
//Your eScript to run
jsEngine.eval(sEscriptToRun);//run


This is the same code that we used to import the translation class in step 4.

When you run the program, it will connect to the server object manager, your eScript will then be converted to the proper Java methods, and run against this JDB session.

Next Steps

Thats all thats needed to create a tool that can run adhoc code on the server. The next step is to build a nice UI so users can open the script file, run it, and display results inline, or create a command line program that can be run from any editor, and return the results back to the console.

Another idea is to allow the user to "simulate" an existing business service from our tool. This tool can also be the foundations of a xUnit testing suite. A Java veteran will easily absorb this information, and make something more usable.

Conclusion

Hardcore readers will be quick to realize that this is not a complete eScript engine. It is probably 90% of an engine that will run most of your code, for example it dosnt implement the C Lib functions, and like the Business Service Simulator, it cannot run against the active UI instance. Non hardcore readers will think, this is too hard core, but I leave this out there for those who want to pick it, learn from the implementation or run with it further, but for time being, you have the basics of a very useful prototyping/testing tool.

Funky Town: Non blocking UI

This post is part of the Funky Town series of articles, where we take ridiculous User Centric Design (UCD) requirements and put them into practice.

Imagine that the user

1. Clicks on a button that kicks off a long running report on the server and gets back immediate control of the UI

2. Navigates to another view that fires off several interfaces to get data back

3. As soon as each interface call completes, the screen refreshes with the new data

4. Minutes later the report finishes and the user is presented with a real time browser notification

If the advantages dont jump at you, this list will make it obvious

1. Non blocking UI
2. No nasty polling to maintain
3. Reduced network traffic
4. Less waiting time
5. Real time server to browser notification
6. Parallel processing

This sounds like some modern website, but with a bit of engineering, we can transform Siebel into a more feature rich application.

Basics

Siebel executes code in two modes
1. Synchronous
2. Asynchronous

Synchronous

Server processes invoked by a users session eg. eScript/WFs run synchronously. In other words, you cannot have two pieces of code run in parallel. Synchronous execution freezes the users screen (including any browser side animation) and forces the user to wait until one process has completed before it runs another process.

Asynchronous

Processes can be made to run asynchronously, by dispatching the execution to a background process. This mode provides control back immediately, but a callback is not provided when that process finishes.

However with a little fancy footwork, we can get the best of both worlds. Dispatching code asynchronously with a callback.

Implementation

We will build a button that fires a piece of code that runs on the server for a 10 seconds. The user will be free to do what they choose, and when the server process is complete, a sexy message slides into view, displaying to the user the number of seconds the code took to run.

Ingredients

1. Ajax

Allows us to execute code in the background and get a callback. We will use javascript to launch our background process and listen for the response.

2. Interface

Since the code is triggered client side, we need a browser accessible Siebel interface that allows us to execute code and return some values.

Pick one of the following or come up with your own

A. SWE Querystring
B. XML Command Block
C. SOAP

3. Read Results

I'm going to pick option A. Options B and C will also work, but require dealing with XML payloads and using HTTP POST in javascript, which I'll leave for EAI developers to play with.

The solution entails using Ajax, which dispatches a call to a background process, essentially running synchronous code in a separate session. The user is free to move on and perform other tasks, when the background process finishes, it returns to the browser which registered a callback, the callback function can then notify the user of the results of the process.

4. Proxy

The proxy BS will act as a wrapper to call the intended process, and add the HTTP response headers. These headers are required because invoking a business service using the query string kicks off a server process, which dosnt return a property set to the browser. The proxy business service will convert the outputs into headers that the browser can understand.

5. Session

If we run Ajax calls against our active session, it will still block our UI, so to get around this problem we need a separate Application to offload and invoke our interface. Your second Application can be SI or HI, as long as its different from your active UI session.

The interaction sequence looks like this



There are two key pieces of code that is required for this to work.

1. Ajax Call

The following code is initiated from a button on the browser, and makes a HTTP GET request, using the SWE Querystring API. This querystring calls a Proxy BS, passing in the destination Business Service, Method, and arguments.

It also registers a callback routine to handle the result from the background process. This callback function can take summary data from the server business service and display the results to the users session or it can detect the users view, and refresh data that has been loaded in the background.

var reqUrl = "/Application2/";
reqUrl+="start.swe?SWECmd=ExecuteLogin&SWESetMarkup=XML&SWEDataOnly=1"+
"&ParamBS="+sBS+
"&ParamCallMETH="+sMeth+
"&SWEAC=SWECmd=InvokeMethod&SWEMethod=test&SWEService=MyProxyBS";

var xmlHttp = new XMLHttpRequest();
xmlHttp.open('HEAD', reqUrl, true);
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status == 200 || xmlHttp.status == 204) {

var sResp = ""+
",Status: "+xmlHttp.status+""+
",Result: "+xmlHttp.getResponseHeader("time elapsed");
alert(sResp);
}
}
}


2. Proxy BS

This Proxy BS written in eScript, handles the invokation of the destination Business Service, and creation of the custom HTTP headers. Once this piece of eScript finishes, it returns to the onreadystatechange handler in the code above.

var oBSWeb = TheApplication().GetService("Web Engine HTTP TXN");
oBSWeb.InvokeMethod("GetAllRequestParameters", Inputs, Outputs);

//Get parameters from querystring
var sBSToCall = Outputs.GetProperty("ParamBS");
var sMethToInv = Outputs.GetProperty("ParamCallMETH");          

//Call your BS
//processing time here....
var sElaspedTime="1000";

//set response headers for the browser to read
Inputs.SetProperty("time elapsed",sElaspedTime);
oBSWeb.InvokeMethod("SetResponseHeaders",Inputs,Outputs);


The example above just uses an alert, but an imaginative developer can implement a sexier dialog, or invoke a refresh on the current view

Summary

I've demonstrated usage of the SWE API, but Siebel provides a similar more supportable architecture in the form of an EAI Object manager, making the SOAP interface a more suitable fit.

This is no simple piece of configuration, and it is not recommended that you attempt it without the consultation of your local Siebel Architect. The intention of this article, is to illustrate how Siebel and browser technologies can come together to achieve a solution that was written off as impossible.

This concept was POC'd for client who needed real time notifications for bulk processing, it allowed the user to continue on, and get a fancy notification once the process was completed.

To satisfy our Funky town series, we have to ask ourselves

1. Will it perform well?
Yes, code can be distributed in parallel, and run in the background

2. Is it maintainable?
Yes, if the code is put into a framework, and maintained by an experienced professional

3. Can it be upgraded?
Yes, the solution can be built using a supported architecture, and abstracted through a business services. and

4. Is it sexy?
Yes, its what Siebel Open UI promises to be, except Open UI dosnt do server side callbacks! (Confirmed with Siebel in 4th Qtr 2011)

EAI Phone Format Challenge feat. JL Engine

BRP

Business Rule Processor (BRP) is a diamond in the configurators toolkit, and it should be at the forefront of Oracle's marketing efforts to license more copies of Siebel. If you've never heard of BRP, then you've never lived Siebel, just imagine an Application that is fast, which can be complex and flexible, and at the same time being virtually script free.

Amazing as it is, it has a few flaws, rules cannot be validated, there are UI bugs that make development annoying, rules dont synchronise across multiple OMs, procedures don't support parameters, but the most obvious flaw is the debugging capabilities, every thing is just dumped to the OM logs, no workflow instance monitor, or TBUI like debugger around here, just old fashion log files.

However the design approach of BRP is apparent, the first release of BRP was meant to deliver a fast and stable tool, anything else was second priority, but there is a question that hangs around the future of BRP, with OPA being crowned the rules engine of choice within Oracle. There seems to be a gap in the strategy, OPA is a policy determination engine, while BRP is an eScript replacement, one is business focused, while the other is technically focused, two different things in my opinion.

So what if you dont have BRP on your project? I was engaged by a customer to design solutions to a common problem, getting rid of a sea of code and improving maintainability. All the standard Siebel declarative and non declarative options were considered and weighed up against another. One of the key components of this redesign was a rules engine, and BRP was the primary choice, but due to various factors, including whether BRP could be sourced. I was engaged to build a BRP replacement: a custom rules engine that overcomes some of BRP's limitations that is declarative and fully customisable.

JL Engine

For the sake of identity, the rules engine is conveniently referred to as JL Engine.

Imagine that you could extend the native Siebel Query Language Expressions, and create your own functions, that can be accessed declaratively. This is a powerful feature that allows developers to bypass many of the complexities around implementing rules using the traditional Siebel expressions, and potentially replace a large amount of scripting.

What kind of complexities do you ask? To fire your imagination, how about we solve the EAI Scriptless Phone challenge using this new engine? Previously I demonstrated the power of BRP, and showed how BRP can be used to solve this challenge. It required 15 expressions to complete the task

If you had control over your expressions, you could design convienent utitlities to more effectively solve this challenge, and also re-use these utilities across the application, or you could cheat and design a fancy expression that solves the EAI phone challenge in 1 step, but in consideration of good application design, we are going to design components which are re usable, hence this new rules engine solves the challenge in 8 steps.

Here are some of the new exotic expressions that will aid us in this Challenge

ApplyMask(Mask,MaskCharacter,Value)
//Usage: ApplyMask("$XX.XX","X","1234") = $12.34

AscFromChar(AsciiCode)
//Usage: AscFromChar("65") = A

GetArrayPart(ArrayString,Delimiter,ArrayIndex)
//Usage: GetArrayPart("A,B,C",",","1") = B

CountChar(Value,CharToCount)
//Usage: CountChar("$XX.XX","X") = 4

Solving the EAI Challenge with JL Engine

1. Getting the LF character

This was the original stumbling block to solve this challenge when it was first announced. I presented a solution using XSLT. Impossible Siebel reader Dos, then chimed in with other 2 solutions.

BS: SSSE Address Parser
Method: GetCRLF

This business service returns a CRLF, which is actually two characters chr(10) + chr(13), so the return string has to be post processed to return only chr(10)

SetProfileAttr("LF","\n")

Using SetProfileAttr to set \n, and retrieving it using GetProfileAttr is elegant, but requires two steps. Using our JL Engine expression below allows us to complete this in one step

AscFromChar("10")


2. Separating the Phone and Format from the EAI Phone String

Once we have the LF character, string expressions can be used get our string. In BRP this is done using two expressions Instr is used to find the position of the LF character, then Left/Right is used to chop the string.

"+44123456789LF(00) 00 000 000"

Using our JL Engine expression below allows us to complete this in one step. GetArrayPart splits the string, and returns the desired index.
GetArrayPart("+44123456789LF(00) 00 000 000" ,"LF","0")

3. Applying the Format Mask to the Phone Value

To make things harder, the phone value has the international prefix, which is additional to the number and should not have the format applied to it. BRP and the original XSLT solution solved this by using two cursors, one to track the position of a Mask character, and the other to track the position of phone digit that corresponds to this mask characters, and involved looping and recursion. Using our JL Engine expression below allows us to complete this in one step (three nested expressions)
CountChar("(00) 00 000 000","0")
Counts the number of format characters, which we then use to perform a Left/Right on the phone string to split the prefix and phone Once we have the phone without the international prefix, we use the following Expression to get the formatted number.

ApplyMask("(00) 00 000 000","0","123456789")
The joined expression looks like this

ApplyMask("(00) 00 000 000","0",Right("+44123456789",CountChar("(00) 00 000 000","0")) )
The final step is to concat the Prefix with the result from the above step to get our result

JL Engine solution step by step

The screen shots below provide the step by step detail, of how the solution is physically implemented in the JL Engine.

 1. Create a rule and define all the properties



 2. Create a Main procedure, and create the 8 steps as shown, thats all.



 3. Test the Rule in Business service simulator, or if you are a lucky customer that has SiebUnits on your project, you could write a declarative test case, to test your declarative rules engine.



The test results applet shows the correct output value for our challenge, with the test case passing in 32ms.

Conclusion

This new custom rules engine passes the EAI Phone challenge with flying colors, and manages to do it more nimbly, with less steps of declaration, thanks to its custom inbuilt functions, but is this enough to make it a superior solution than BRP? Both engines are built for different purposes, but share a lot of similiarities.

BRP is a speed demon, and was built for handling complex calculations, which just happened to come with a declarative engine that allows a project to banish casual scripting, but sadly it is not a universal module, and looks likely to be sidelined from future updates.

JL Engine was also designed to remove scripting, comes built with connectors to a custom application framework, it achieves speed by taking shortcuts and also provides a declarative interface for developers, but best of all it is openly extensible, allowing developers to build upon the gaps of BRP with custom requirements.

Want a real time instance monitor that shows step by step execution, condition determination and variable assignment. You got it, JL Engine comes with a rule audit history view to do just this. Want a visulizer that shows the rule composition in a graphical UI? No problem, JL Engine comes with this as well. Want a expression builder that allows developers to build rules faster? Yes Sir, we have that covered.

So how about some performance figures? In the next episode of the EAI Scriptless phone challenge. I will compare all the declarative solutions to this challenge, and provide performance metrics to determine which is the fastest and the best, so be prepared to be blown away.

1. XSLT using Xalan Engine
2. XSLT using Saxon Engine
3. BRP Engine
4. JL Engine

Note 1: While other solutions exist that simply strip away the phone format mask, the above script less solutions apply the format to the phone and have the flexibility to deal with the international prefix. 

Note 2: The JL Engine is not available commercially. The product has a limited audience, and is only available to selected customers.

InvokeMethod with Parameters

Requirement

Capture a response from client side, and send it to the server

Option 1: Use a profile attribute

Profile attributes are dirty for a number of reasons, this post by Mike M. Lin, highlights the issues very well.

Option 2: Invoke a server side business service

This option requires a business service to be exposed to the browser, and requires abit more code to invoke, but it is certainly an option, especially if you need a return value. The only caveat is that the code on the server dosnt have direct context of the BC, but it is a good way to encapsulate your logic.

Option 3: Invoke method??

InvokeMethod in server script has no parameters, InvokeMethod in browser script contains the "inputPropSet" parameter, but unfortunately it can not be used to pass data to the server side, however with a little fancy foot work, we can bend InvokeMethod to handle parameters.

The solution entails scripting, and abuse of a Siebel staple, so its not for the faint hearted, but I know if you've read this far, then you are here to see Siebel bend, so read on...

Modified Requirement

Pass an array of data eg. ["I","love","disco"] and send it from browser side to server side

Solution

1. Create a button on an Applet

2. Set the Method Invoked property = "EventMethodDataHandler"

This method is only a proxy, the real data shuttling happens in the code below

3. Create the following browser script in Applet_PreInvokeMethod

function Applet_PreInvokeMethod (name, inputPropSet)
{
var iRet="ContinueOperation";
var aArr=["I","Love","Disco"];
switch(name){
case "EventMethodDataHandler":
this.InvokeMethod("EventMethod|YourBrowserMethodName|"+aArr.join(","));
break;
}
return iRet;
}

The above code constructs a dynamic InvokeMethod that appends data to the method name itself.

4. Create the following server script in BusComp_PreInvokeMethod

function BusComp_PreInvokeMethod (MethodName){
//ComplexMethod - processing for invoked methods with args
var aComplexMethod=MethodName.split("|");
if(aComplexMethod[0]=="EventMethod"){
iRet= PreInvokeMethod_ComplexMethod(aComplexMethod);
return iRet;
}
}
function PreInvokeMethod_ComplexMethod(aComplexMethod){
var sMethod=aComplexMethod[1]; //Custom Method
var sArg1=aComplexMethod[2]; //Args
switch(sMethod){
case "YourBrowserMethodName":
var aArrFromBrower=sArg1.split(",");
//perform your processing here
break;
}
}

By using a standard namespace, we can detect whether the method invoked is a Complex method or a normal method invocation, and handle it accordingly.

In the above example. The method invoked is

EventMethod|YourBrowserMethodName|I,Love,Disco

The server code intercepts method this by detecting the namespace, and converts the method name into an array. The last argument of the array is a String representation of our data which was set dynamically by the browser. This data can be any type of object that can be serialised, but InvokeMethod was not designed to transport data, so if you do decide to use this option, restrict it to small amounts of data.

Beware that Siebel encodes HTML entities, so if you want to use these type of characters as values in your data, you'll need to build some helper utilities to deal with converting them.

This method of parametizing the InvokeMethod is not limited to browser script, but it does open doors to perform funky browser/server co-ordination and offers an alternative to abusing your profile attributes.

SiebUnit - Build Inventory

This article is a continuation on SiebUnit - an xUnit implementation, and provides an inventory of the parts needed to construct a SiebUnit framework.

Like the first article, this is aimed at the Technical Architect, who understands the concept, and knows how to put the necessary pieces together to bring this idea to life.


Architecture



The architecture diagram allows us to visualize important concepts at a high level, and can be used to break up big jobs into more manageable pieces.


Rules Engine

You can replace Rules Engine with any Siebel interface that supports receiving Inputs and returning Output PropertySets. SiebUnits will also work against a standard invoked business service, or workflow.

The only caveat is that you cannot have context, that is, you must re-design your BS/WFs to decouple your logic from any specific Application context. This naturally suits the testing of a rules engine, external API such as ESB, internal APIs like custom business object libraries, and scripting utilities.


SiebUnit Framework

A SiebUnit framework can be composed of the following sub components

* Test Case Runner
* Test Case start up routines
* Test Case tear down routines (optional)
* Test case assertion
* Random data generator
* Expression language
* Expression language parser
* Expression Executor
* Expression Builder
* Test history and performance timing
* Script Library
* Business Object (BO) Library
* XML Parser
* SQL/IO Layer


Test Case Runner

This component reads the configuration data from the data store, loads all the test cases in memory, and runs each test case one by one. It records start, stop times, and logs the output, but does not make any determination on the test case result. Errors are captured, and any exceptions are suppressed, allowing the Test Case Runner to continue on until it completes all test cases.


Test Case Startup/TearDown

In order to test certain scenarios, prerequisite test data may need to be setup before the rules can be tested. This component constructs the initial test data before the test suite runs. Optionally, there can be a Tear Down component, which cleans up after all test cases.


Test Case Assertion

Assert is a computer science term used to ensure a condition is true/false to enforces application integrity. The assertion component performs the same role, it checks the outputs of each test case, and validates it against the expected result for that test case, logging the result to a history data store.


XML Parser

This component converts XML into a structured object and back. This can be used to send dynamic outbound messages, and validate inbound responses, for integration testing.

SQL/IO Layer

This component retrieves the related data from an entity for validation purposes. It is used by the Test Case Assertion component.


Random Data Generator

Even within predefined test boundaries, we can allow the generation of random data to simulate dynamic conditions.

Eg. Generate a bunch of expense activities of random type, and amount, and test it against a business rule for assignment or approval logic.

The random data generator can be used by the Setup component, or the individual test case to randomize inputs into a rule.


Expression Language

To make SiebUnits declarative, we need a way of allowing the developer to construct data, supply dynamic inputs, and validate the results of the test cases, without writing a line of code. This can be done via an Expression Language, similar to calculated field expressions in Siebel.


Expression Language Parser

This component is responsible for translating the above expressions into a form which is understandable by the Siebel eScript compiler or external engine, that will in-turn run the instructions.

The expression parser needs to differentiate between a literal and expression. This is done via 4 ways, as illustrated throughout Siebel.

1. Field Pre/Post Default

Expressions need to be prefixed with "Expr: " or a key word such as "System", "Field" etc. Anything else is considered a literal.

If you wanted to pre default another field via the square bracket notation "[Id]" this will actually result in a literal value

2. Calculated Field

A calculation is always expected to be an expression, unless it is surrounded by quotes. So the square bracket notation "[Id]" is treated as an expression in this context.

3. Field Type

This example can be seen in WFs in the Output argument of some steps. There is another field that indicates the type. Eg: Literal vs Expression.

4. Dot Notation

WFs also have another interesting expression, with the dot notation to reference child elements within property sets. This dosnt really fit into either of the above 3 categories. As it can easily be interpreted as a literal, and does not have any prefixes to indicate that it is an expression.

You have to make a call on which method you will use, and your expression parser has to recognise the above patterns if you mix the scenarios.

Eg 1. For an eScript implementation, the expression

"GetProfileAttr('Me.Id')"

Should translate this to an eScript call like this

TheApplication().GetProfileAttr("Me.Id")

The parser has to recognise that this is an expression with a literal argument.


Eg 2. The next expression provides a dynamic input.

"GetProfileAttr([&Property])"

This follows Siebels WF standard, of referencing WF properties via the ampersand prefix inside square bracket notation, requires a bit more work because you have to deal with the nesting of expressions.

This is where you can draw the line, and define styles for your expressions and have another expression to deal with that scenario

"GetProfileAttrByProperty('Property')"

Now the literal references a property that holds the value to this method. You will have to plan out your expression language in advance, and know how your parser will deal with each scenario.


Expression Executor

After you've translated the expression into the native language, it needs to be executed, and the result returned. The method that you execute the expression, will impact on the style of your expressions, and how complex your parser needs to be.

Consider implementing a short circuiting mechanism so that, if part of a condition expression is met, you can abort executing the rest of the expression.


Expression Builder

This component allows the developer to choose from a defined set of expressions, this aids in learning, reduces mistakes, and as well as documenting all the supported functions of SiebUnit. Ideally It has to integrate with the Siebel UI session, and apply the expression to the list/control.


Test History and Performance timing

This data is produced by the Assert component which validates the test case runners run history with the expected outputs, it also extracts performance timings for analysis.

Inputs and Outputs of the run are also included in the run history to help debugging. Having the results in the UI is consistent of the expectations of a declarative tool. Take note BRP engineers!

Test history and performance data can be transient, and the user can export the results, if a base line needs to be established.

Script Library and BO Library

It is important to differentiate between the above two components. A script library provides utilities, and prototypes on native objects, while a BO library provides business functions.

A SiebUnit library is tightly integrated with the script library for performing internal operations, and provides proxy functions for invoking the BO library to setup and perform test cases. When creating framework functions you will have to make conscious decisions in which library a function belongs to.


Unit Testing and Regression Testing

SiebUnit does not replace commercial regression testing suites. An important differentiation is that SiebUnits is designed to be used by developers for unit testing code/WFs before it is checked in, and to ensure application integrity is maintained before it reaches the testers.

SiebUnits are also more thorough, as only the developer could understand all the exception paths of a program. SiebUnits test technical functionality, while regression test tools test functionality.

Although SiebUnit can be used to regression test business rules, it does not test UI components. The developer is still expected to ensure that UI rules are unit tested. A badly configured link, can still bring the system down and while writing SiebUnits to create data, and check the data returned from the link could be a valid test case. It was not intended to micro manage every piece of individual configuration.

SiebUnits can offer a great sense of security for the developer, but poorly designed test cases, can also provide a false sense of security. This is normally due to poor test case coverage. Average developers will usually test the positive scenario, good developers test both positive and negative scenarios. SiebUnits require the developer to understand all the possible logic paths and design test cases to thoroughly cover off the scenarios that can result the program to fail.

Conclusion

There are numerous benefits to having SiebUnits on a project. The immediate return on investment, is that your testing period is reduced, and your application will be more robust, but the real payoff of really comes down the track, when you build highly complex systems, where one small change can break another part of the system. SiebUnits can provide your developers with confidence to make that change, without costly regression testing. SiebUnits will keep your Application, and also your sanity in check.

xUnits is a proven concept, and is a staple of any modern development platform. SiebUnit provides an evolution path, so that your developers no longer need to manually unit test. The world has JUnit , jsUnit, CUnit, OCUnit, PHPUnit, PyUnit, SUnit and now we can add SiebUnit to the list.

SiebUnit - an xUnit implementation

Introduction

Over a year ago, I presented a concept of xUnits in Siebel and how they could be used. Because it was only a concept, it was missing implementation details, so there was some doubts to whether Test driven development would work in Siebel, where code is meant to be restricted to fringe cases.

At that time, I ported some code from a back end Java project that I was working on, to automate Siebel interface verification. The idea was to pickup XML templates from a file system, insert randomize data, send it to the ESB, and validate the results.

It was used for several releases, reducing a days worth of effort to verify 50 interface interactions, to about 5 minutes. With the JUnits in place, they could be used to verify an integrated environment instantly on demand, instead of employing highly paid professionals to perform repetitive regression testing.

The tool was written in Java, didnt have a UI, it had access to the eApps SSO hashes, which meant that an evil developer could use it to log onto a server environment, and impersonate anyone they wanted. It was more of a regression testing tool than a unit test tool, and being written in Java, it was inaccessible to most of the Siebel Team.

In the spirit of a true Siebel developer, I thought a real implementation of xUnits in Siebel had to be scriptless.

So "SiebUnit" was designed to be just that, a fully declarative unit testing framework for Siebel developers.

This framework also happens to plug into a rules engine like BRP, OPA, or it could be run against a collection of business services/workflows, as long as you can pass in an Input and get an Output property set, the "SiebUnit" framework handles the rest.

This idea was sold to a Client, who immediately picked it up as a way to get developers to unit test, improve build quality, and reduce dependence on testers, and provide a structured development process.

This article and its followup, takes a step further, providing architectural and implementation concepts. It is aimed at the Technical Architect, who can grasp high level concepts, fill in the low level implementation, possibly innovate on this idea, and introduce SiebUnits into your Siebel project.


What is SiebUnit

If you had to sell the concept of xUnits in Siebel, this slide should sum it up.



This screenshot shows a SiebUnit implementation that was built for a Client.



Features

Notification System for test results

Uses a custom notification system, written in a client side library, to add persistent messages to the current view. The messages can be dismissed, by clicking the X button.


Design and Run Test Cases from the UI

Test Cases are configured, executed and debugged all within a standard Siebel UI. Debugging the rules itself, depend on the capability of the rules engine. Eg. BRP can only be debugged via log files, WFs can be debugged using the instance monitor.


Export/Import test Cases

Allow easy export and import of hierarchical test configuration data between different environments.


Trace Toggling

Toggles developer tracing, for performance testing of rules


Dynamic test data

Uses XML templates, and proxy functions to the BO Library to generate unique test data. Random data can be generated and provided on inputs, or overridden in the case of XML templates.


Expression Builder

A helper utility designed for the developer to construct the test cases declaratively. This is built using HTML, which integrates with the Siebel session.


Other

SiebUnits comes with pre built internal test cases to validate your eScript engine, and profiles ST objects vs non ST objects.

SiebUnits can be configured to run recursively to load test an environment


Case Study - Custom Rules Engine

SiebUnits was used on a project to ensure the integrity of a Rules Engine.

The design of the custom rules engine started off in small components, followed by unit testing, further build and some more unit testing. The components were then integrated, followed by another round of unit testing.

As the design got exponentially more complex, the integration of all these components, made it impractical to regression test all the exception paths, after every change.

SiebUnits was originally built with integration to BRP. So for this project, it was modified to work with this new engine.

A collection of SiebUnits was built to test the following aspects.

Declarative IF conditions
Declarative loops
Declarative procedures
Expression Translation
Expression Results
Business Service Invocations
Property Assignment
Cancel Propagation
Error Messages
Math Operations
Rule inheritance
PropertySet Interactions


Some further internal test cases were also designed to validate the behaviour of the eScript engine. This ensures that if Siebel changes the eScript engine, these internal SiebUnits will pick up on the expected outputs of dependent core functions, and allow the root cause to be addressed.

Once the SiebUnits were designed, and configured, they were used to repeatedly validate the Rules Engine after every change, to ensure previously built functionality still functioned. Changes often broke other unexpected parts of the engine, due to the tight integration and dependency between components, but with the SiebUnits in place, the report of failed test cases helped to quickly pinpoint the cause.

Towards the end of the build, a critical defect was discovered with the eScript engine, that corrupts a core object of the rules engine. This happens during random runs of the test suite, but it was easily reproducible using the load runs.

The problem required a re-design of the core object, which unfortunately touches every function in the engine. Since the new object had a different interface to the old object, it required a re-design of most of the interactions around this object as well.

The affected parts of the program was identified, and a fix was implemented in two hours. The SiebUnits test suite was run afterwards to ensure that the rules engine passed all the test cases. This provided a QA stamp on the product, and was similiar to having a team of regression testers on hand to validate your work at will.

The collection of SiebUnits also allowed load testing of the engine in a real environment. During these load runs, server resources can be monitored to ensure that they run within acceptable standards and allow capacity planning based on expected usage.

It would have been much more difficult if not impossible, to complete such a complex build without SiebUnits in place, and the regression testing and maintenance effort would have been an ongoing burden.


Intermission

Do you have BRP, OPA or a collection of Business Services that you would like to keep in check? The next article will look at the architecture of SiebUnits, and all the components that need to come together to form the framework of SiebUnit.

Note: The term Rules Engine is used broadly in this article. Even though BRP stands for Business Rule Processor, it is actually a declarative eScript replacement engine. OPA is more suited to the term for a Rules Processor, as it takes a bunch of inputs, makes a determination and returns the result. However the Rules Engine interface of Inputs/Outputs is what makes it suited to SiebUnits.

View the next article here