Monday, July 9, 2012

Siebel 8

It's been a while...

Good news - with no modification to code the Framework will run with Siebel 8.1 (8.1.1.5 to be specific) - all that was needed was to add an user property under the application to add a reference to the business service, so with Name=ClientBusinessServiceXX Value=MFramework

Matt

Tuesday, August 2, 2011

Framework SIFs - updated download link

A few potential downloaders have let me know that the original download URL no longer works.

I've updated it, so hopefully this one will work...


Source Code


As with the last time, contained in the .zip file above are a few files, which I'll briefly run through:

1. MFramework.sif

This is a SIF file of the business service that contains both the server side (eScript) elements of the Framework, and the corresponding Browser Script side. This business service is contained in a project called "MFramework".


2. MFramework_BS.sif

This is a SIF of just the business service, without containing project, if you want to import into a different project.


3. MFramework Service Wrapper WF.xml

This is a workflow process used by the asynch business service calling. As it currently is, this is contained in the project called MFramework.


4. MFramework_bs.js

If you'd prefer just to view the code in your favorite editor this file is the entire browser script side of the business service...


5. MFramework_ss.js

... while this is the server eScript of the business service.


Matt

Tuesday, August 3, 2010

Correction to Code - from Getting Started with SIFs

In the last post I had a couple of lines of code added to the browser script and server script of the application that you use... Neel posted a comment to say he was having some trouble using 8.1 - I'm not sure that this error is to blame, but it can't be helping!

Hopefully this correction will help get the code at least running.

So, the correct code for the server script declarations section of the application you are using should be:

var oBS:Service = this.GetService("MFramework");
oBS.InvokeMethod ("BuildFramework", this.NewPropertySet(), this.NewPropertySet());

while the following is the correct code for the new method in the browser script of the application you are using:

function BuildFramework()
{
    var bsUi = theApplication().GetService("MFramework");
    bsUi.InvokeMethod("BuildBsFramework",theApplication().NewPropertySet());
};

I've added this. and theApplication(). in front of the calls to the NewPropertySet() method, which hopefully will help things run.

Monday, July 19, 2010

Getting Started with SIFs

Having downloaded the source files this should help get things started:

1. Import the business service and the workflow process.

2. Add the following line to the config file in the appropriate place (in the [SWE] section)

ClientBusinessServiceXX  = "MFramework"

3. Check in and Deploy the WF Process "MFramework Service Wrapper WF" (if you want to use the asynch business service calling feature).

4. Add the following to the server script declarations section of the application you are using:

var oBS:Service = this.GetService("MFramework");
oBS.InvokeMethod ("BuildFramework", NewPropertySet(), NewPropertySet());

5. Add the following as a new method in the server script of the application you are using:


function FrameworkHandle()
{
    return this.GetService("MFramework").frameworkHandle();
}

This acts as a wrapper for BCs, BSs etc to get a reference to the Framework object.

6. Add the following to the browser script declarations section of the application you are using:

setTimeout("BuildFramework()", 1000);

7. Add the following as a new method in the browser script of the application you are using:


function BuildFramework()
{
    var bsUi = theApplication().GetService("MFramework");
    bsUi.InvokeMethod("BuildBsFramework",NewPropertySet());
};

8. Compile and generate browser scripts.

Run the application.

To test we'll run some of the browser side functionality (which in turn runs the server side, so is a good way to smoke test)... so when the application is running copy and paste the following into the address bar:

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1", ["Full Name"])["Full Name"]);
//

Hopefully you'll get an alert with "SADMIN SADMIN" here, if so, great, it works!

Some more tests:

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1", ["Full Name"]).Full_Name);
//

Same result as above, just accessing the value in a subtly different way.

javascript:alert(top.oFramework.Lov.getDescription("LOV_TYPE","LOV_TYPE"));
//

Here you should get a description of the LOV_TYPE lov.

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1","").Created);
//

So that should give the Date of when the SADMIN Employee record was created.

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1","").Created.toDb());
//

Same as last test but displays in DD/MM/YYYY format. This makes use of two features of framework - new methods added to JS datatypes, and optional typing of fields in the BusComp.getRecord() method.

So hopefully that all works, let me know if not and I'll try and figure out why.

For info., I use 7.7 and the ST engine - I did have strong typing in the code, which I have removed to allow this work in the T engine, but there may be some  other problems with the way I use the JS objects, so let me know if you've any issues or thoughts.

And let me know if it works too, of course!

Matt

Framework SIFs

As promised here are some SIFs that should hopefully make getting this running a little bit easier, or at the very least make the code a little easier to follow.


Source Code


Contained in the .zip file above are a few files, which I'll briefly run through:


1. MFramework.sif


This is a SIF file of the business service that contains both the server side (eScript) elements of the Framework, and the corresponding Browser Script side. This business service is contained in a project called "MFramework".


2. MFramework_BS.sif


This is a SIF of just the business service, without containing project, if you want to import into a different project.


3. MFramework Service Wrapper WF.xml


This is a workflow process used by the asynch business service calling. As it currently is, this is contained in the project called MFramework.


4. MFramework_bs.js


If you'd prefer just to view the code in your favorite editor this file is the entire browser script side of the business service...


5. MFramework_ss.js


... while this is the server eScript of the business service.


I'll create a further post to give a quick "how to" list to get started with this shortly...


Matt

Saturday, July 10, 2010

Coming soon...

Folks, just thought I'd post a quick note about some items in the pipeline... I'm on holidays at the moment, so haven't been near a computer in a while.


Puli posted an interesting comment on the lack of security on the browser script side - remember this passes a call down to the server side, where an eval is used. Well, with some playing around I got some unauthorised code to run. I'm not sure what mischief could be caused by this, but this is an unexpected problem - I've some examples of this in action, and a (hopefully) straightforward fix.

The Framework uses a technique described in Oracle Doc Id 579955.1 - which basically shows how to directly run methods on a cached business service (avoiding the dreaded InvokeMethod and property sets!). It has been suggested to me that caching business services can cause problems in later versions of Siebel. (I'm using Linux at the moment, and the Oracle support site seems to take exception to this for some reason and won't allow me in so I can't check!) - I'll post an alternative way to make the Framework work without the BS caching (and no visible difference to the callers of the Framework).

Finally, the SIFs - a good few comments saying the code is great, which is nice, but not necessarily straightforward to cut and paste, and expect to get working. I have some SIFs nearly ready to go, I just need to remove some specific code from a few places... I do have these, but not actually tested them yet, so once tested will post up.

Matt

Friday, June 18, 2010

From Browser Side, Down, then Back Up

As a topic that will cover a lot of the features of the Framework I thought tracing a call from browser script down to the server side then back up would be interesting. As an example I'll take getting a field value from applet browser script.

Now this is something that should be really straightforward - but isn't. Really, you're limited to only the fields that are displayed on the applet - there are some tricks to get other fields (hidden columns, anyone?) but it always seemed that this should be easier. Then what if you want a field from a BC not on the applet?

This Framework offers a way to do this - and good news - it can be done in a line! (We'll, a line is added to your applet browser script, a few more lines of code get used)

Just for simplicity we'll display a personalised alert to the current user.

So we add the following line to the applet's browser script:

alert("Hello there " + top.oFramework.BusComp.getRecord("Employee.Employee", theApplication().LoginId(), ["Full Name"]).Full_Name);

And that's it.

This will do what it looks like -display a "Hello there" message to the current user (querying the Employee BC). This will run on any applet (in fact any browser script anywhere) and doesn't need to have the "Full Name" field shown. It doesn't even need to be anything to do with the Employee BC!

Ok - how does this work?

Well starting in the application browser script here a call to a business service is made (the browser script side of the framework, here. Now the method called, BuildBsFramework() is empty. The real work is done in the declarations section. There are two lines here:

ExtendJsObjects();
top.oFramework = new Class_Main();


These will be discussed in a later post, but in summary the first extends JS objects (currently String and Date) while the second instantiates a Class_Main object, and attaches to top, naming it "top.oFramework"

Within the Class_Main there is an object BusComp that encapsulates functionality relating to Business Components - in the case of the Browser Script version here all it does is expose several wrappers - so getRecord is one. Again, we'll discuss the methods (and their signatures in a later post) - for the moment let's look at what this does:

getRecord: function () { return this.methodWrapper("BusComp.getRecord", arguments); },


So this calls a second method; methodWrapper; with an argument "BusComp.getRecord", then the arguments array passed into it - so: "Employee.Employee", theApplication().LoginId(), ["Full Name"];

methodWrapper is shared by the various browser script objects:

function Method_Wrapper(sName, vArguments)
{
 var oArguments =[];

 oArguments[0] = sName;

 for(var x=0;x<vArguments.length;x++)
 {
  oArguments[x+1] = vArguments[x];
 }

 return top.oFramework.callServerFramework(oArguments);
}


so this takes the two arguments it gets - the name and the arguments array and essentially joins them into one (arguments does not seem to be a "true" array object, so the above technique is used in place of using push() or concat() say.)

Now the callServerFramework method of the Framework object is used. Within this there is essentially a normal call using a business service (so don't forget to add its name to the config file, but worry not, this is the last time you'll ever have to do this!). There are a couple of interesting lines:

oInputs.SetProperty("Method", oArguments.shift());


So the first argument (which in this case is "BusComp.getRecord" is assigned to an input property called "Method"). The next line of interest is:

oInputs.SetProperty("Arguments", this.toSource(oArguments));


So this sets a second input property called "Arguments" to be the return of "toSource()" when passed the remainder of the arguments array (which in this case would be "Employee.Employee", theApplication().LoginId(), ["Full Name"])

Here is the source of toSource():

toSource: function (obj)
{
    var sReturn = ""; 
    if (typeof(obj)=="string") // strings - need to quote, also replace ' with \'
    {
        sReturn = "\"" + obj.replace(/\'/g,"\\\x27") + "\"";
    }
    else if (typeof(obj.shift)=="function") // arrays
    {
        sReturn += "[";
        for (var i=0; i<obj.length; i++)
        {
            sReturn += this.toSource(obj[i]) + ", ";
        }
        sReturn = (sReturn.length==1) ? sReturn + "]" : sReturn.substr(0, sReturn.length-2) + "]";
    }
    else if (typeof(obj.getTime)=="function") // date
    {
        sReturn = "new Date(\"" + obj + "\")";
    }
    else if (typeof(obj)=="object") // objects - loop through properties
    {
        sReturn += "{";
        for (var x in obj)
        {
            sReturn += (x.indexOf(" ")>-1) ? "\"" + x + "\":" : x + ":" ;
            sReturn += this.toSource(obj[x]) + ", ";
        }
        sReturn = (sReturn.length==1) ? sReturn + "}" : sReturn.substr(0, sReturn.length-2) + "}";
    }
    else // number, boolen, function will be left
    {
        sReturn = "" + obj;
    }
    return sReturn;
}


What this does is take any JS object and convert it into its source: a String. (So an object that has a property called "Name" with a value of "Value" and a second property called "Array" that has the value of an array with 1,2 and 3 in can be represented as follows:
{Name:"Value", Array:[1,2,3]}


By representing an object in this manner it doesn't matter how complex the object is: it will be represented as a single string. This means that every call can ultimate share a common server side method - no matter what its input looks like, the input the server side method will always be a string representing the method name and a second representing the arguments.

On the server side the method RunFrameworkMethod the important logic is:
var sFrameworkMethod = Inputs.GetProperty("Method"); //Framework method to run
var sArguments = Inputs.GetProperty("Arguments"); //String of arguments
var oReturn; //Return value, may be object or string

//Strip [] from around arguments
sArguments = sArguments.substring(1, sArguments.length-1);

// clear stack
o_LocalFramework.clearStack();

//Build up command: oFramework.method(Arg1, Arg2,...);
var sCommand = "oReturn = o_LocalFramework." + sFrameworkMethod + "(" + sArguments + ");";

//Run command
eval(sCommand);

//If an object is returned, convert to string and set return type to Object
if(typeof(oReturn.shift) == "function" || typeof(oReturn) == "object")
{
 Outputs.SetProperty("Return Type", "Object");
 Outputs.SetProperty("Return Value", o_LocalFramework.toSource(oReturn));
}
//Otherwise return value and set return type to Primitive
else
{
 Outputs.SetProperty("Return Type", "Primitive");
 Outputs.SetProperty("Return Value", oReturn);        
}


take a look at this line:
var sCommand = "oReturn = o_LocalFramework." + sFrameworkMethod + "(" + sArguments + ");";


so we've built a variable called sCommand that in this example case will be:
"oReturn = o_LocalFramework.BusComp.getRecord("Employee.Employee", theApplication().LoginId(), ["Full Name"]);"


The next line of interest is:
eval(sCommand);


So very, very, simple, yet very, very powerful - this runs our server side call for us.

Now while eval may well be "evil", and allows us run anything at all, we've kept some of the "evil" in check here in the way we have built the command we are running - it always starts with "o_LocalFramework." (rather than allowing the caller specify the full command, which adds some safety. So what we'll run will be some of the server side function.

So we have a variable, oReturn, which is the return from: o_LocalFramework.BusComp.getRecord(...) - so the server-side framework method. (Again, this will be discussed in a later post). getRecord returns a JS object. For the minute just assume one of the properties this object has contains the name of the Employee record queried; the property is called "Full Name" and the value "MattW".

The following will be run:

Outputs.SetProperty("Return Type", "Object");
Outputs.SetProperty("Return Value", o_LocalFramework.toSource(oReturn));


The first property indicates that the return has a complex type, the second is an echo of the browser-side toSource() from earlier - so the object is flattened into a single string. This allows us use the same method for calling any number of methods - we're not limited to a specific return type.

Back in callServerFramework() in the browser side Framework we're onto this:

return this.toObject(oOutputs.GetProperty("Return Value"));


So the return property (a JS object in string form) is passed into a method called toObject. Again we're using the power of eval:

var oT = eval('(' + source + ')');


What we have now is oT which will be a proper JS object, as if we'd built it here in browser script, so we can access its attributes in the usual way, so (remembering our assumption) our original line:

alert("Hello there " + top.oFramework.BusComp.getRecord("Employee.Employee", theApplication().LoginId(), ["Full Name"]).Full_Name);


is equivalent to:

alert("Hello there " + obj.Full_Name);


which in this example will be:

alert("Hello there MattW");


Great - from BS down, and all the way back.

(yes, yes, MattW isn't a *full name*, but this is an example!)

So hopefully that all makes sense. Next post I'll have a look at what in some of the server side logic - so BusComp.getRecord may be a good place to start.

Matt