Writing and executing scripts

Content Script scripts are "document" class objects stored on Content Server. The primary usage for a script is its execution. When you "execute" a script, you are basically programmatically invoking a series of APIs that perform actions over Content Server's or other systems' data. In the following paragraphs, we are going to analyze all the Content Script architecture's elements and components that play a role in turning a textual file into an actionable object.

As said, scripts are persisted as "documents" on Content Server. Whenever you execute a script a component named Script Manager retrieves the script's last version and, either compiles it (and caches the compiled version) or loads a pre-compiled version of it for execution. Scripts' execution is managed by another component named Content Script Engine. The Content Script Engine executes the script's code against the provided execution context (the execution context is the "container" through which the script's code can access the Content Script's services, environment variables, support variables, database, etc..). The internals of both the Script Manager and the Script Engine are not relevant for the purpose of this manual and won't be discussed.

On the other hand the execution context plays a fundamental role in the process that allows you to write and execute scripts. Knowing the content and the features of this object will help you in writing your scripts.

How execution contexts are created

The Script Manager creates the most appropriate execution context on the basis of:

  • the script's code;

  • the system's current configuration;

  • the user context (user's permission, user's roles, etc..)

  • the cause that triggered the script's execution (direct invocation, scheduler, callback, etc..)

Execution context

Upon execution, every Content Script is associated to a Groovy binding. The binding can be seen as a container for Objects that are part of the context in which the script is executed. We make reference to this context as Content Script Execution Context or as Script Binding

The Script Manager initializes the Script Binding before execution, injecting a set of objects, which include:

  • API Services

  • Request variables

  • Support Objects

  • Support Variables

Additionally, a set of script utility methods are available in the Content Script (Base API). The methods grant access to short-cuts for commonly used features or can pilot the execution result.

API Services

programmatically schedule/unschedule script execution, impersonate other users (requires administrative privileges)

When the script Execution Context is initialized by the Content Script engine, a set of service objects (the Content Script API) are injected. These objects allow a Content Script to perform operations on Content Server, to use internal utilities (such as PDF manipulation utilities or the templating service), to access external systems and services, etc.

Disable services

Services that are available for usage within Content Scripts can be enabled/disabled through a dedicated configuration page in the AnswerModules administration pages.

Here after are some of the main services that are currently available as part of Content Script APIs.

API Name Description
Base API Base API is constituted by methods and properties that are exposed directly by each script. Some of the most important API are: logging, redirection (used to redirect users navigation through a server side redirection i.e. http code 302, outputting HTML, XML, JSON and Files
docman The docman service is the main access point to the Content Server Document Management functionalities. With docman service is it possible, among other things, to: create and manipulate documents and containers, access and modify meta-data, access and modify object permissions, access volumes, perform database queries, manipulate renditions and custom views, run reports, consume OScript request handlers, programmatically import/export content through Content Server native XML import/export feature
users The users service is the main collector for all APIs related to Content Server users and groups. With users service is it possible, among other things, to:: create/modify/delete users and groups, impersonate different users, access and modify user privileges, perform member searches
workflow The workflow service allows to programmatically manipulate workflows. With the workflow service is it possible, among other things, to: :start, stop, suspend, resume, delete workflows, access and manipulate workflow and task data, accept, complete, reassign workflow tasks, perform searches within workflows and tasks, change workflows' and steps' title
search The search service allows to programmatically search over Content Server's repository. With the search service is it possible, among other things, to: easily build/execute complex search queries programmatically, easily build/execute query based on categories attributes, retrieve search result with or without pagination
collab The collab service is the main access point to the Content Server collaborative functionalities. With collab service is it possible, among other things, to::create and manage projects, tasks and milestones, create and manage discussions, list and manage users’ assignments
mail The mail service allows to programmatically create/send and receive emails from scripts. With the mail service is it possible, among other things, to: create and send email message through multiple mailboxes, scan mailboxes and retrieve incoming messages and attachments, create email messages (both html and text messages are supported) with custom templates, send email to internal users and groups, attach files and Content Server documents to emails, configure multiple email service profiles to use different IMAP/SMTP configuration at the same time
template The template service can come in handy anytime you have to dynamically create documents. With the template service is it possible, among other things, to: evaluate documents and plain text strings as templates, replace place holders and interpret template-expressions
admin The admin service allows to programmatically perform administrative tasks. With the admin service is it possible, among other things, to: perform XML import/export operations, programmatically schedule/unscheduled Content Script executions
classification The classification service is the main access point to the Content Server classification features. With classification service is it possible, among other things, to:access, apply, remove classifications from objects
pdf The pdf service allows to programmatically manipulate PDF documents. With pdf service is it possible, among other things, to: create and manipulate PDF documents, write in overlay on PDFs, extract PDF pages as images, merge PDFs, add watermarks to PDF documents, add barcodes (mono and bi-dimensional) on PDF pages, remove print/modify permissions from PDF, add PDFs in overlay to existing PDFs, extract images from pages or portion of pages, read bar-codes form PDF’s pages, remove/insert pages
ftp The ftp service allows to interact with FTP services. With ftp service is it possible, among other things, to: access, read, write files and folders on multiple FTP servers
docx xlsx The docx/xlsx services allow to programmatically manipulate Microsoft Office documents. With docx/xlsx services is it possible, among other things, to: create and manipulate Word, PowerPoint and Excel documents, read and write documents' properties
forms The forms service is the main access to the Content Server web-forms features. With forms service is it possible, among other things, to: create and modify form and form template objects, read/modify/delete submitted form records, submit new form records, export/import form records
adlib The adlib service allows to programmatically drive the AdLib rendition engine. With adlib service is it possible, among other things, to: create jobs for AdLib PDF Express Engine and fetch renditions results
rend The rend service allows to programmatically invoke external rendition engines. With rend service is it possible, among other things, to: transform on the fly HTML pages to PDF documents, rend WebForms as PDFs, invoke external services through an "all-purpose" generic rendition api
sap The sap service allows to integrate Content Script with the well known SAP ERP through RFCs. With sap service is it possible, among other things, to: connect to multiple SAP systems through JCO APIs, invoke standard and custom SAP functions to retrieve/update ERP information

APIs evolution

New service APIs are constantly added or updated with every subsequent release of Content Script. Optional APIs are usually available through Content Script Extension Packages, and can be installed separately using the master installer or the extension packages’ own installers.

Request variables

Request variables are variables injected into the execution context by the Script Manager whenever a script is directly invoked as a result of a user's browser request.

Variable Description
params A container for the Script’s request parameters. It’s a non-case sensitive map that provide access to all the parameters passed to the script when executed.
In the params map are injected by default also the following variables (where available):
  • myurl:The URL string used to execute the Content Script
  • node: the id of the Content Script object
  • useragent: the user's browser useragent
  • cookies: the user's browser cookies (as strings)
  • method: the HTTP verb used to request the script
  • lang: the user's locale
  • port: the HTTP port used to request the script
  • server: the HTTP host used to request the script
  • pathinfo: the request's URL path information
request A synonym for the previous variable (for backward compatibility)

Support variables

The number and the nature of the variables that are injected by the Content Script Engine depends primarily from the mode through which the script has been executed. Content Script scripts used for example to implement Node Callbacks or columns' Data Sources will have injected in their Execution Context, respectively: the information regarding the Node that triggers the event or the Node for which the column’s value is requested. Please refer to the Content Script module online documentation for the name and type of the variables made available in the Execution Context in the different scenarios. The following variables are always injected.

Variable Description
img Content Server static resource context path (es. /img/).
webdav WebDav path
supportpath Content server support path
url Content Server CGI Context
SCRIPT_NAME A synonym for the previous variable (for backward compatibility)
csvars A map containing the script's static variables
originalUserId The ID of the user that triggered the execution of the Script (not considering impersonation)
originalUsername The username of the user that triggered the execution of the Script (not considering impersonation)

IMG

Please note that most of the time the img context variable ends with a trailing slash. To correctly used it as a replacement variable in Content Script strings or velocity templates we suggest you to use the ${img} notation. E.g.:

"""<img src="${img}anscontentscript/img/am_logo.png" />"""

Support objects

Support objects are instances of Content Script classes that the Script Manager creates, configures and injects into every execution context in order to provide a simple mean for accessing very basic or commonly required functionalities.

Variable Description
self An object representing the Content Script node being currently executed.
response An instance of the ScriptResponse class that can be used to pilot the Content Script output.
gui A map of standard Content Server UI Components that can be enabled/disabled at the time of rendering the page.
E.g.
  • gui.search = false
  • gui.sideBar = false

Disable standard UI

To completely disable the standard Content Server UI use:

gui.gui = false 

log Each Content Script is associated with an instance logger that can be used to keep track of the script execution. From within a script you can access the logger either using the Script’s method getLog() or the shortcut log. The Content Script logging system is based on a framework similar to the one used internally by OTCS. The logger supports six different levels: trace, debug, info, warn, error. The default log level for any script is: error this means that log messages at level for example debug won’t be outputted in the ModuleSuite’s master log file (cs.log).

Logging level can be override per script basis through a dedicated administrative console.

out A container for the script textual output

Base API

The Content Script "Base API" or "Script API" is constituted by methods and properties that are exposed directly by each Content Script script.

API Description
asCSNode(Map) An alternative to loading a node explicitly using one method out of: docman.getNode, docman.getNodeByPath, docman.getNodeByNickname
asCSNode(Long) An alternative to loading a node explicitly using the docman.getNode method
redirect(String) A shortcut for sending a redirect using the response object
json(String) A shortcut for sending json using the response object
json(Map) A shortcut for sending json using the response object
json(List) A shortcut for sending json using the response object
sendFile(File[,String]) A shortcut for sending a file using the response object
success(String) A shortcut for setting the result of the script execution to "success"
runCS(Long) A utility method to run a second Content Script (identified by ID) within the same context
runCS(String) A utility method to run a second Content Script (identified by nickname) within the same context
runCS(String, Object[]) A utility method to run a second Content Script (identified by nickname) using a cleaned execution context (the new execution context shares with the caller’s context only the Content Script services and the following variables: out, gui, response). In the sub-script code the parameters that have been used to call the sub-script can be accessed through the context variable “args”. Using this variant it’s possible to intercept the result of the sub-script execution.
printError(Ex) A utility method to print out any exception raised by script’s execution

Examples

Usage example for runCS(String, Object[]) API

//Parent Script
node = asCSNode(123456)
map  = runCS(mySubScript, node, users.current)
out << map.user

//SubScript “mySubScript”
def retVal = [:]
retVal.name = args[0].name
retVal.user = args[1].with{
 [
    name:it.displayName,
    id:it.ID
 ]
}
return retVal
Usage example for asCSNode(...)API

// Load a CSNode
asCSNode(2000)

// A node can be loaded also by path or nickname
asCSNode(nickname:"MyNode")
asCSNode(path:"path:to:myNode")
asCSNode(id:2000) //=== asCSNode(2000)

Usage example for printError(...)API

try{
    out << asCSNode(12345).name
}catch(e){
    log.error("Error ",e) //Prints the full stack trace in the log file
    printError(e) //Outputs the error

}

Script's execution

As shown in previous sections, the execution of Content Scripts can be triggered in different ways. Here after are a few examples:

  • Direct execution by a user. This can happen, for example:

    • Using the Execute action in the object function menu or promoted object functions

    • While using the Content Script Editor, using the Execute or Execute in Modal buttons (useful for debug and testing purposes, shown in the figure below)

    • A URL associated to the execution of a Content Script is invoked

    • A Content Script backed SmartUI widget is displayed

  • Direct execution by an external system

    • A URL associated to a Content Script REST API is invoked
  • Automatic execution by the system. This happens when:

    • The script is scheduled, at the configured execution time

    • A callback is configured, and the associated event is triggered

    • A Content Script Workflow step is configured as part of a workflow, and the step is activated

    • A Content Script is configured as a Data Source for a WebReport, and the WebReport is executed

    • A Content Script serves as a Data Source for a custom column

Script's output

As you can easily imagine by analysing the examples in the previous paragraph, the expected result from the execution of a Content Script varies significantly from case to case.

When a user executes a Content Script directly from the Content Server user interface, he/she would probably expect, in most of the cases, the result to be either a web page, a file to download, or a browser redirection to a different Content Server resource.

When a remote system invokes a REST service API backed by a Content Script, it will most probably expect structured data in return (probably XML or JSON data).

When a Content Script is executed as part of a workflow and the next step is to be chosen depending on the execution outcome, the script will probably be expected to return a single variable of some kind (a number or a string) or an indication that the execution was either successful or encountered errors.

Content Script is flexible enough to cover all of these scenarios. The next section will include examples of how to provide the different output necessary in each situation.

HTML (default)

The default behaviour in case of a successful script execution is to return the content of the "out" container

def contentToPrint = "This content will be printed in output"
out << contentToPrint
def contentToPrint = "This content will be printed in output"

//If the object returned by the script is a String, it will be printed in output
return contentToPrint

JSON

JSON content can be easily returned

def builder = new JsonBuilder()
builder.companies {
    company "AnswerModules"
    country "Switzerland"
    }

// Stream JSON content, useful for restful services
response.json(builder)
String jsonString = '{"key":"value"}'

// A string containing JSON data can be used 
response.json(jsonString)
// or with the shorthand method
json(jsonString)
// or
json([[key:value1], [key:value2]])

XML

XML content can be easily returned

gui.gui = false
gui.contentType = "application/xml"

def builder = new StreamingMarkupBuilder()
def parent  = asCSNode(2000)
def nodes   = parent.childrenFast //nodes are lazy loaded 

def xml = builder.bind {
    node(id:parent.ID, name:parent.name,  isContainer:parent.isContainer){
        children {
            nodes.collect {
                node(id:it.ID, name:it.name, isContainer:it.isContainer)
            }
        }
    }
}
out << XmlUtil.serialize(xml)
Output of the above script:

<node id="2000" name="Enterprise" isContainer="true">
    <children>
            <node id="90064"    name="Import"   isContainer="true"/>
            <node id="3270165"  name="Training" isContainer="true"/>
    </children>
</node>

Using gui support object for tuning script's output

Note the usage of gui.contentType in order to change the response’s “Content-Type” header.

Files

It is also possible to stream a file directly:

// Stream a file as result of the execution
def res  = docman.getTempResource("tempRes", "txt")
res.content.text = "Just a test"
def file = res.content 
response.file(file)
// Stream a file as result of the execution
def res  = docman.getTempResource("tempRes", "txt")
res.content.text = "Just a test"
def file = res.content 
// Stream a file, specifying if it is a temporary file (will prevent deletion) 
response.file(file, true)
// Stream a file as result of the execution
def res  = docman.getTempResource("tempRes", "txt")
res.content.text = "Just a test"
def file = res.content 
// or with the shortcut method
sendFile(file)
// Stream a file as result of the execution
def res  = docman.getTempResource("tempRes", "txt")
res.content.text = "Just a test"

// or returing the CSResource directly
res.name = "My textFile.txt"
return res
Managed resources

In the context of developing against OTCS you will end up dealing with many different kind of contents most of which are (or a strictly related with) files. In order to reduce the amount of code needed to properly manage the disposition of temporary files, Content Script introduces the concept of "managed resource" or CSResource. A CSResource is basically a wrapper around the File class. CSResources are managed by the Content Script engine (no disposition required) and are returned any time you want to access the content of a CSDocument or you fetch a version from it (in these cases the CSResource will keep a reference, towards the source CSDocument, through its "owner" property.

CSResources are first class citizens in Content Script. A CSResrouce can be for example returned directly by a Content Script, triggering the download of the same.

Returning CSResource to trigger document download

Returning a CSResource from a script is the simplest way to stream out a file in this case is important to keep in mind that the name of the downloaded file will be determined using the following rule:

if the property onwer of the CSResource is != null                                                                                                                                                         
then                                                                                                                                                                                                       
    use the name of the CSNode referenced by the CSResources owner property                                                                                                                                   
else                                                                                                                                                                                                       
    use the CSResource's name property.
end 

Redirection

In alternative, the response could contain a redirection to an arbitrary URL:

String url = "http://www.answermodules.com"

// Send a redirect using the response
response.redirect(url)


// or with the shortcut method
redirect(url)
// or
redirect ${url}/open/2000
// or
redirect asCSNode(2000).menu.open.url

HTTP Code

In certain cases (e.g. when Content Script is used to extend OTCS’ REST APIs), it could be necessary to explicitly control the "error" or "success" status of the script execution:

// Force the script execution result to be "success" using the response
response.success("This is a success message")
response.success("This is a success message",200)

// or with the shortcut method
success("This is a success message")
success("This is a success message",200)

// Force the script execution result to be "success"
response.error("This is an error message", 403)

// or with the shortcut method
error("This is an error message", 403)

Advanced programming

Templating

Content Script features a flexible yet powerful templating engine based on Apache Velocity. Evaluating a template is just a matter of invoking one of the evaluate methods available through the template service.

Content Script velocity macros

Content Scripts defines a collection of macros that simplify the creation of OTCS UI embeddable interfaces. A developer can create his own macros simply defining them in a z_custom.vm file to be stored under the Content Script "Temp" folder (as defined in the Base Configuration page: amcs.core.tempFilePath).

Name and description Param Type and description Usage example
csmenu(dataid[,nextUrl])
Creates the standard OTCS context menu for the given node (identified by its dataid)
dataid Integer
node's dataid
#csmenu(2000)
nextUrl String
csresource(retList)
Loads static libraries from the module support directory
resList List
A list of resources to load. To be chosen from: query, jquery-ui, jquery-ui-css, bootstrap, bootstrap-css
#csresource([‘bootstrap’])
csform(script[,submit])
Creates the HTML form needed to submit a request against the executed Content Script
script Integer
The objId of the Content Script you’d like to execute
#@csform()
//Custom form inputs go here
#end
submit String
The value for the label of the submit button. If null the submit button will not be created
cstable(columns,sortColumn,
columnsClasses[,checkBoxes])

Creates an HTML table that fits nicely with the standard OTCS UI
columns List
The list of column labels
#@cstable([‘First Name’], {},{}, true)
//Your rows here
#end
sortColumns Map
A map of “Column Label”, “Property” couples. The Property is used to build sort links for columns
columnsClasses Map
A map of “Column Label”, “CSS Classes” couples. The “CSS Classes” are assigned to the THs tags.
checkBoxes Boolean
If TRUE the first column of the table will have width 1%. To be used to insert a checkboxes column
cspager(skip,pageSize,
pagerSize,elementsCount)

Creates a pagination widget to be used
skip Integer
The index of the element to skip before to start rendering rows
#cspager(0 25 3 $parent.childCount)
pageSize Integer
The page size (e.g. 25)
pagerSize Integer
The number of pages to show in the pager widget
elementsCount Integer
The total number of elements

OScript serialized data structures

Content Script Java layer is tightly bound with Content Script Oscript layer, thus quite frequently you will face the need of managing Oscript's serialized data structures obtained for example querying the OTCS' database or from nodes' properties.

Oscript serializes its data in the form of Strings, for this reason Content Script enhances the String class in order to provide a quick method for retrieve the corresponding Content Script’s objects out of the OScript serialized representation.

Methods available on the String class are:

  • getDateFromOscript

  • getListFromOscript

  • getMapFromOscript

In the exact same way Content Script enhances its most common types (List, Map, Date, Long, CSReportResult) in order to simplify the creation of the corresponding OScript serialized representation.

The table here below shows an usage example of the mentioned features:

Optimizing your scripts

Behaviors

You can use behaviors to decorate your scripts and let them implement a specific set of new functionalities. Behaviors are to be consider similar to inheritance. A behavior is defined as a collection (MAP) of closures and usually implemented in the form of a static class featuring a getBehaviors method.

When you add a behavior to your script, all the closures that have been defined in the behavior become part of your script thus becoming part of your script context.

Behaviors are resolved at compilation time, this means that they should be considered as a static import.

Said otherwise, any changes applied directly on the script that implements your behaviors, won't effect the scripts that have imported such behaviors. In order to update the imported behaviors you have to trigger the re-compilation of the script that is importing them (target script).

BehaviorHelper

**I**n order to add behaviors to a script you shall use the BehaviourHelper utility class.

The BehaviourHelper utility class, features three methods:

@ContentScriptAPIMethod (params = [ "script" , "behaviours" ], description = "Add behaviours to a Content Script" )
public static void addBeahaviours(ContentScript script, Map<String, Closure> closures)

@ContentScriptAPIMethod (params = [ "script" ,  "behaviours" ], description= "Remove behaviours from a Content Script " )
public static void  removeBehaviours(ContentScript script, String... closures=null)

@ContentScriptAPIMethod (params = [ "script" ,  " behaviour " ], description= " Determine if the script already has the specified behaviour " )
public static void  hasBehaviour(ContentScript script, String name)

Through BehaviourHelper you can add, remove or check for the presence of an associated behavior.

Behaviors are of great help to structure your code base, optimize executions and reduce boilerplate code.

Module Suite comes with few predefined behaviors, you can easily implement yours by defining a map of closures to be passed to the above BehaviourHelper utility class.

Default Behaviours

The AMController behavior has been designed to simplify the creation of form-based application on Content Server.

It features the following closures:

  1. start: this closure takes no parameters, and it is used to dispatch incoming requests. It creates (if not already provided) an app object to be made available in the execution context. It analyzes the request's pathinfo, to extract the information required to route towards a registered closure. Rebuilds any Beautiful WebForm object found in the request.

    This closure should be the last instruction of your script.

    app =  [:]
    app.product ="Module Suite"
    
    if(!BehaviourHelper.hasBehaviour(this, "start") ) {
        BehaviourHelper.addBeahaviours(this, AMController.getBehaviours())
    }
    
    home = {
        out << "Hello world from ${app.product}"
    }
    
    details = { String id = null->
        out << "This script ID ${id?asCSNode(id as int).ID:self.ID}"
    }
    start()
    
    When directly executed (http://my.server/otcs/cs.exe?func=ll&objId=12345&objAction=Execute&nexturl=.. or http://my.server/otcs/cs.exe/open/12345) the script above will output:

    Hello world from Module Suite

    when executed using: http://my.server/otcs/cs.exe/open/12345/details it will output

    The script ID 12345

    when executed as: http://my.server/otcs/cs.exe/open/12345/details/2000 it wll output:

    The script ID 2000

    In other words the requested path will always been interpreted using the follow schema: http://my.server/otcs/cs.exe/open/12345/closurename/param1/param2/param3 where closurename will be defaulted to "home" if not found in the path.

  2. loadForm(def formID, def amSeq=0): loads a Form data object, setting form.viewParams.contentScript = params.node (so that if the form data object will be used with a BeautifulWebForm view the form will submit on this very same content script) and form.viewParams.amapp_Action = params.pathinfo.

  3. submitForm(def form): validates the form data object and performs the submit (executing pre-submit and on-submit scripts if defined)

    renderForm(def form, def context=null): renders the form either in the script context or in the specified context