Skip to main content

Advanced code examples

Please refer to basic code examples document for naming conventions and logging strategies.

Note:

Object scripts can be written in Java or JavaScript (which will be executed by the Rhino engine, just like the executed fields), but good practice is to prefer Java language which
include a compilation step and ensure that the syntax of the script is correct. In advanced use cases that are not part of this tutorial, the use of Java gives access > to all of the classic application development tools: step-by-step debugging, unit tests, development in a Java IDE, code quality analysis with Sonar etc..

Examples are provided both in Rhino and Java so as you can see the syntax differences.

Sharing parameters

It can be useful to store parameters (or serializable objects) in the user's session (Grant) or object instances (ObjectDB)

Object-level parameter

  • Share parameters between screens in the user's navigation: a component needs to know from where it has been opened...
  • Use some parameters in Action: asynchronous or recursive calls to limit the stack size (with heap memory)
  • Use some parameters in External objects (not thru http parameters on each browser calls)

Examples:

Share a parameter between objects:

Java

@Override
public void initUpdate() {
getGrant().setParameter("MYAPP_CONTEXT_ID", getRowId());
super.initUpdate();
}
@Override
public void initList(ObjectDB parent) {
// To use the current Id of A when a list B is displayed
String id = getGrant().getParameter("MYAPP_CONTEXT_ID");
if (!Tool.isEmpty(id)){
// ...
}
super.initList(parent);
}

@Override
public Object display(Parameters params) {
// To use the current Id of A when the external object is displayed
String id = getGrant().getParameter("MY_CONTEXT_ID");
if (!Tool.isEmpty(id)){
// ...
}
}

public void myAction() {
// To use the current Id of A when a list B is displayed
String id = getGrant().getParameter("MY_CONTEXT_ID");
if (!Tool.isEmpty(id)){
// ...
}
}
Rhino JavaScript equivalent
MyObjectA.initUpdate = function() {
// To store the current RowId of this object
this.getGrant().setParameter("MYAPP_CONTEXT_ID", this.getRowId());
}

MyObjectB.initList = function(parent) {
// To use the current Id of A when a list B is displayed
var id = this.getGrant().getParameter("MYAPP_CONTEXT_ID");
if (id && id!="") // ...
};

MyExternalObjectC.display = function(param) {
// To use the current Id of A when the external object is displayed
var id = this.getGrant().getParameter("MYAPP_CONTEXT_ID");
if (id && id!="") ...
};

MyObjectB.myAction = function() {
// To use the current Id of A when a list B is displayed
var id = this.getGrant().getParameter("MY_CONTEXT_ID");
if (id && id!="") ...
};

Store a set of data (as org.json.JSONObject between hooks of the same object:

Java

@Override
public List<String> postValidate() {
JSONObject data = new JSONObject().put("key1", "value").put("key2", 123).put("key3", new JSONArray().put(new JSONObject()/* ... */));
getGrant().setParameter("MY_DATA", data.toString());
return super.postValidate();
}
@Override
public String postSave() {
JSONParser parser = new JSONParser();
JSONObject data = (JSONObject) parser.parse(getParameter("MY_DATA"));
AppLog.info(data.toString(),getGrant());
String k1 = data.getString("key1");
int k2 = data.getInt("key2");
JSONArray k3 = data.getJSONArray("key3");
// ...
return super.postSave();
}
Rhino JavaScript equivalent
MyObject.postValidate = function() {
var data = new JSONObject().put("key1", "value").put("key2", 123).put("key3", new JSONArray().put(new JSONObject(...)));
this.setParameter("MY_DATA", data);
}
MyObject.postSave = function() {
var data = this.getParameter("MY_DATA");
console.log(data.toString());
var k1 = data.getString("key1");
var k2 = data.getInt("key2");
var k3 = data.getJSONArray("key3");
// ...
}

Etc.

Global parameter

To make a global setting, it is necessary to use the system singleton

Grant.getSystemAdmin().setParameter(name, value);
Grant.getSystemAdmin().getParameter(name);
Grant.getSystemAdmin().removeParameter(name);

Session parameter

The best solution is to load the parameter depending on user in the GrantHooks at logon:

  • To request only once any external resource (LDAP, ...) to retrieve external data, rights...
  • Or read some fields added to the object User or in the local database

Example:

Java

@Override
public void postLoadGrant(Grant g) {
try {
String login = g.getLogin();
String employeeId = g.simpleQuery("select ... query depending on login ...");
g.setParameter("MYAPP_EMP_ID", Tool.isEmpty(employeeId)?"unknown" : employeeId);
String empPhone = Tool.readUrl("http://...external REST service...");
g.setParameter("MYAPP_EMP_PHONE", empPhone);
} catch (IOException e) {
AppLog.error(e, g);
}
super.postLoadGrant(g);
}
Rhino JavaScript equivalent
GrantHooks.postLoadGrant = function(grant) {
var login = grant.getLogin();
var employeeId = grant.simpleQuery("select ... query depending on login ...");
grant.setParameter("MYAPP_EMP_ID", employeeId || "unknown");
var empPhone = Tools.readURL("http://...external REST service...");
grant.setParameter("MYAPP_EMP_PHONE", empPhone || "");
};

Booby traps:

  • name: prefix the names of the grant-level parameters with your unique project code to prevent any conflicts
  • value: store small values/objects in memory (parameters are in the user's session = the JVM heap)
  • use the removeParameter to free memory when the parameter has been used (if not parameters will expire with the session)

Advanced validations

Phone number validations

As of version 3.1 MAINTENANCE 07, it is possible to do an advanced validation of phone numbers fields (typically in a preValidate or postValidate hook).

Example:

Java

try {
setFieldValue("myPhoneNumber", new PhoneNumTool("fr").getNationalNumber(getFieldValue("myPhoneNumber")));
} catch (ParamsException e) {
AppLog.error(e, getGrant());
}
Rhino JavaScript equivalent
var f = this.getFieldValue("myPhoneNumber");
f.setValue("myPhoneNumber", new PhoneNumTool("fr").getNationalNumber(f.getValue()));

Note: it is also possible to format as international number using getInternationalNumber instead of getNationalNumber

Data preparation

Dynamic list generation

In order to programmatically generate a list of values, you have to:

  1. assign a non-empty static list of values to the field, as you would do for a normal list (this is to avoid "empty list" errors to be triggered by the platform)
  2. build the dynamic list in the appropriate object's hook:
    • in the postLoad hook if the list is fixed for the duration of the user's session
    • in the postSelect hook if, for instance, the list depends on the current record
    • etc.

Example:

Java

@Override
public void postLoad() {
ObjectField field = getField("myField");
field.setList(new ObjectFieldList(field)); // Empty the configured list
// Build list (here the next 10 years)
ObjectFieldList list = field.getList();
int year = Tool.parseInt(Tool.getCurrentYear(), 2000);
for (int i = year; i <= year + 10; i++)
list.putItem(new EnumItem(String.valueOf(i), this.getGrant().T("YEAR") + " " + i)); // enum item = (value, label)
super.postLoad();
}
Rhino JavaScript equivalent
MyObject.postLoad = function(){
var field = this.getField("myField");
field.setList(new ObjectFieldList(field)); // Empty the configured list

// Build list (here the next 10 years)
var list = field.getList();
var year = Tool.parseInt(Tool.getCurrentYear(), 2000);
for (var i = year; i <= year + 10; i++)
list.putItem(new EnumItem(i.toString(), this.getGrant().T("YEAR") + " " + i)); // enum item = (value, label)
};

Data encryption

As of version 3.2 you can use the EncryptionToolclass to encrypt/decrypt a field value. EncryptionTool uses the system parameter ENCRYPTION_ALGORITHM (defaults to AES).

Encryption/decryption methods using String produce/consumes Base64-encoded strings.

Example:

Java

public String key() {
// ZZZ set as a system parameter (make sure to configure it as "private") ZZZ
//return getGrant().getParameter("MY_ENCRYPTION_KEY");
// or
// ZZZ pass this to the JVM by -Dmy.encryption.key=...
//return System.getProperty("my.encryption.key");
// or
// ZZZ set this in the JVM environment
return System.getenv("MY_ENCRYPTION_KEY");
// etc.
}
@Override
public String preSave() {
// Encrypt the value before saving
try {
ObjectField l = getField("mySensitiveField");
l.setValue(EncryptionTool.encrypt(l.getValue(),key()));
} catch (EncryptionException e) {
AppLog.error(e, getGrant());
}
return super.preSave();
}
@Override
public void postSelect(String rowId, boolean copy) {
// Decrypt the value after selecting it
ObjectField l = getField("mySensitiveField");
try {
l.setValue(EncryptionTool.decrypt(l.getValue(), key()));
} catch (EncryptionException e) {
AppLog.error(e, getGrant());
}
super.postSelect(rowId, copy);
}
Rhino JavaScript equivalent
MyObject.key = function() {
// ZZZ set as a system parameter (make sure to configure it as "private") ZZZ
//return this.getGrant().getParameter("MY_ENCRYPTION_KEY");
// or
// ZZZ pass this to the JVM by -Dmy.encryption.key=...
//return System.getProperty("my.encryption.key");
// or
// ZZZ set this in the JVM environment
return System.getEnv("MY_ENCRYPTION_KEY");
// etc.
};

MyObject.preSave = function() {
// Encrypt the value before saving
var l = this.getField("mySensitiveField");
l.setValue(EncryptionTool.encrypt(l.getValue(), MyObject.key.call(this)));
};

MyObject.postSelect = function(rowId, copy) {
// Decrypt the value after selecting it
var l = this.getField("mySensitiveField");
l.setValue(EncryptionTool.decrypt(l.getValue(), MyObject.key.call(this)));
};

Note: an encrypted field using this method cannot be searchable except of exact values (by encrypting the search filter in the preSearch hook)

Since version 6.0, you can use the hook fieldEncryptDB

  • To be called automatically on form, list (edit list...), search
  • but also in more UI context: crosstab, redolog...

Example: Java

private String getKey() {
// ZZZ set as a system parameter (make sure to configure it as "private") ZZZ
//return getGrant().getParameter("MY_ENCRYPTION_KEY");
// or
// ZZZ pass this to the JVM by -Dmy.encryption.key=...
//return System.getProperty("my.encryption.key");
// or
// ZZZ set this in the JVM environment
return System.getenv("MY_ENCRYPTION_KEY");
// etc.
}

/**
* Encrypt or decrypt the field value
* @param f Object Field
* @param value Field value (crypted or decrypted)
* @param encrypt true to encrypt the value, false to decrypt
* @param context create/update to encrypt, select/redolog to decrypt
* @return crypted or decrypted value
*/
public String fieldEncryptDB(ObjectField f, String value, boolean encrypt, String context) {
if (f.getName().equals("mySensitiveField")) {
return encrypt
? EncryptionTool.encrypt(value, getKey())
: EncryptionTool.decrypt(value, getKey());
}
return super.fieldEncryptDB(f, value, encrypt, context);
}

Call remote URL with client certificate

In this example the object is storing the client certificate JKS file as a document field and the certificate password as a password field.

It can be easily transposed with the JKS avialable as a static local file or as a (protected) resource and with the password stored as a system parameter or a environment variable etc.

Java

public String callAPI() {
try {
String url = "https://myremotehost/myservice";
DocumentDB cert = getField("myClientCertificateField").getDocument();
String pwd = getFieldValue("myClientCertificatePasswordField");
AppLog.info("Calling " + url + " with client certificate " + cert.getName(),getGrant());
return Tool.readUrlWithClientCert(url, cert.getBytes(true), pwd);
} catch (IOException e) {
AppLog.error(e, getGrant());
}
return "";
}
Rhino JavaScript equivalent
MyObject.callAPI = function() {
var url = "https://myremotehost/myservice";
var cert = this.getField("myClientCertificateField").getDocument();
var pwd = this.getFieldValue("myClientCertificatePasswordField");
console.log("Calling " + url + " with client certificate " + cert.getName());
return Tool.readUrlWithClientCert(url, cert.getBytes(true), pwd);
};

Note: the client certificate must be a JKS file, if you have a PEM certificate you can convert it to JKS format converting it first as PKCS12 using openssl pkcs12 -export -inkey mycert.key -in mycert.pem -out mycert.p12 and then importing it in a JKS file using keytool -importkeystore -destkeystore mycert.jks -srckeystore mycert.p12 -srcstoretype PKCS12 (you will be prompted to enter the passwords for the certficates)

Asynchronous code

This exemple to implement a monitoring of CSV exports launched in parallel.

// Launcher, example : Action button
public String myActionMethod() {

JobQueue.push("myExport123", new Runnable() {
@Override
public void run() {
ObjectDB obj = getGrant().getObject("myExportObject123", "MyObject");
CSVTool.export(obj...);
}
});

and tracker thread, example : external object to display the counter

@Override
public String display(Parameters params) {
// same object instance
ObjectDB obj = getGrant().getObject("myExportObject123", "MyObject");
return obj.getParameter(ImportExportTool.EXPORT_PROGRESS);
}