Currently XPages only allow for scripting in Javascript. There is no direct way that you can call LotusScript from an XPage. However, Server Side Java Script does have access to the full Notes API. Through this you can call any agents you have in your databases. If you have business logic that you need called already in agents, then this is sufficient. If not, agents themselves, can call into your script libraries and access business logic contained in those libraries. It should be remembered that the LotusScript UI classes (NotesUIWorkspace, etc) cannot be called in this library, since the invoking code is an agent. All that remains is to be able to pass parameters from your XPage to the agent, and receive return values from the agent and use them in your XPage. We will demonstrate that here using a profile document as an intermediary store for the parameters and return values.
The Lead Manager is a sample application that shows how to create an XPage front end to an existing suite of Notes databases. In the picture above we can see a sub panel (implemented with an XPage custom control) where the details of a company are produced. Several of the fields (e.g. Name, Business Type, etc) are directly presented from the data in the Notes document representing the company. However, the "Credit Rating" field is not. It is a calculation made by business logic.
It's very easy to display a field that come directly from back end data. For the Annual Revenue field, we create a computed label. The formula for the label can very simply be set from the data source tied to the page using Expression Language.
To display a value based on an agent, it is more complicated. We add another label with a computed value to the custom control on the XPage, but we set it to use a Server Side JavaScript formula. The return value from this formula is displayed as the label contents. Lets walk through that code
JavaScript code to call an agent to operate on parameters, and return a value.
|
|
var unid = "GetCreditRatings"+java.lang.System.currentTimeMillis();
|
|
|
var companyName = CompanyForm.getItemValueString("ContractorName");
|
|
var db = session.getDatabase(DB_SERVER(), DB_CORE_PATH());
|
|
create profile document for parameters
|
|
var profDoc = db.getProfileDocument("GetCreditRatings", unid);
|
|
|
profDoc.replaceItemValue("companyName", companyName);
|
|
|
profDoc.replaceItemValue("replyID", unid);
|
|
|
|
|
|
var profDocID = profDoc.getNoteID();
|
|
get agent and call it with profile doc
|
|
|
var agent = db.getAgent("GetCreditRatings");
|
|
print("Calling agent"+agent.getName()+" with "+profDocID);
|
|
|
|
|
|
|
|
profDoc = db.getProfileDocument("GetCreditRatings", unid);
|
|
|
|
|
|
|
requestScope.creditRating = "";
|
|
|
|
if (profDoc.hasItem("creditRating")) {
|
|
|
var creditRating = profDoc.getItemValue("creditRating")[0];
|
|
|
requestScope.creditRating = creditRating;
|
|
|
|
print("No credit Rating!");
|
|
|
requestScope.creditRating = "";
|
|
|
|
|
|
|
|
|
|
|
|
return "$"+requestScope.creditRating
|
For this technique, a profile document is used as an intermediary storage place for the parameters to be passed, and the return value to be retrieved. A profile document was chosen over a normal document since we can be sure that there will be no view that inadvertently shows this document. So it should work in a wide variety of databases. To use a profile document, though, we need a profile name and a unique key. The name we can tie to the functionality we are invoking. On line (1) we create a unique key based on the current time in milliseconds. Although this is not necessarily a perfect mechanism (there is a small chance on a very busy, very fast server that two requests could be processed in a single millisecond) it is a pretty safe bet.
The back end business logic we are calling requires a company name to be passed to it as a key for the lookup. At line (2) we extract the key information from the data source on the page. We then place that into the profile document in line (6).
We can direct an agent to operate on a specific document. We notify it that we want it to work on this specific profile document by extracting it's NotesID (9) and passing that when we invoke the agent (13). However that does not tell the agent how to respond to us. We don't have it write back to the same in-memory document, since that can creating problems with caching. To be sure both halves of the operation are in sync, we want to delete the profile document and recreate it. So, what we do is pass to the agent the unique key we used to create this profile document in a field in the document itself (7).
Once everything is set up, we save the profile document (8), retrieve the agent from the database (), and invoke it (13). This operation blocks until the agent has completed running. Since this is done during a page refresh, it is very important not to call long running business logic in this matter or even slightly time consuming business logic on a very busy server.
Once the agent is finished, we need to get our return value back. We recall the profile document on line (15). We extract the value for the field from the document (25) and set the value into a request scope variable (26). If some failure occurs we set a neutral value into the request scope (19), (31). We use the request scope here as a convenient way of passing a value around some of our server side JavaScript code. We could just as easily use a local variable. But because this value is "expensive" to retrieve, we wanted to leave the possibility open that we might use this value elsewhere on the page. For example we might want to display a graphic of a thermometer with a different level set based on the credit rating. As long as we can guarantee that the display of that graphic will always be within the same request call as the display of this label then the image graphic could make use of that request scope variable instead of calling the agent again.
Lastly we remove the profile document (35) and return the value we retrieved (36). The results are shown in Image 1.
On the other side of the fence we have the agent's code. This can be done by any valid agent. For this example we use LotusScript. The first task of the agent is to work out what document contains the parameters.
LotusScript function to calculate the parameter doc, and pass it to the processing function.
|
|
|
Dim ssn As New NotesSession
|
|
|
|
|
|
|
|
REM This agents reads the pased in doc for parameters,
|
|
REM and writes the result back to the document.
|
|
Set db = ssn.CurrentDatabase
|
|
Set agent = ssn.CurrentAgent
|
|
|
Set doc = db.GetDocumentByID(agent.ParameterDocID)
|
|
|
|
Print "Oops, doc is nothing"
|
|
|
|
|
|
|
|
|
|
The Document ID can be retrieved by a function on the agent and used to get at reference to the document (11). We then pass that document to the processing function (15).
LotusScript function to process the agent's arguments, call the business logic, and pass back a return value.
|
Sub process(db As NotesDatabase, doc As NotesDocument)
|
|
Dim companyName$, credit$, replyID$
|
|
Dim replyDoc As NotesDocument
|
|
|
companyName$ = doc.GetItemValue("companyName")(0)
|
|
|
replyID$ = doc.GetItemValue("replyID")(0)
|
|
|
credit$ = calculateCreditRating(db, companyName$)
|
|
|
|
|
|
Set replyDoc = db.getProfileDocument("GetCreditRatings", replyID$)
|
|
|
replyDoc.ReplaceItemValue "creditRating", credit$
|
|
|
|
|
|
The processing function first extracts the parameters from the passed in profile document (5). It also retrieves the unique ID of the profile document to create as a response (6).
Once it has all the required parameters, it can call the business logic (7). This could be more code in the agent, or code residing in a LotusScript library. Existing LotusScript code on a form cannot be called (although validation logic is called). If you want to make use of that, it is suggested that you first promote that code to a LotusScript library. Then change your form to use the code form the library. Then both the agent and your rich form use the same code base. Any changes or improvements you make to that code will be used by both.
Once the parameters are extracted, the profile document can be removed (8) and recreated (9). Again, this is done to avoid cashing issues that might otherwise come up. More advanced programmers can explore other ways of refreshing the in-memory document for more optimal performance.
The return value is written back to the profile document (10) and saved (11). The value is now all ready for when the agent terminates and the server side JavaScript thread resumes.
So, the above technique is useful for exposing business logic contained in script libraries via an agent. This is the best way to make business logic written in LotusScript accessible to XPages. There is a more efficient and more native way to incorporate business logic that is implemented in Java. For this and other techniques please see
this article here.