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.
API Services¶
Content Script API Service¶
Content Script APIs are organized in classes denominated services. Each Content Script API service acts as a container for a set of homogeneous APIs (API releated to the same kind of objects or features). Content Script APIs can be extended creating and registering new services.
Content Script APIs are, in their most essential form, the methods exposed by the service classes. In order to be recognize as a Content Script API a service class method must be decoretad with the @ContentScriptAPIMethod
annotation.
Content Script API Services Interfaces
When working with Content Script APIs developers program against interfaces. As a matter of fact all Content Script API services and objects implement one or more interfaces. Implementation classes can be easily distinguished from their interfaces because their name ends with the "Impl" suffix.
Content Script API Objects¶
Content Script APIs return or accept, as parameters, objects representing OTCS objects or features. In Content Script, these objects are referred to as Content Script API objects. Content Script API objects are active information containers. We define them active because they expose APIs designed to manipulate the information stored in themselves.
In order to be recognize as a Content Script API Object a class must be decoretad with the @ContentScriptAPIObject
annotation.
When the script Execution Context is initialized by the Content Script engine, all registered API services are injected into it. These services 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.
Here after are some of the main services that are currently available as part of Content Script APIs.
API Service 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 it is 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 it is 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 it is 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 it is 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 it is 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 it is 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.
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 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..)
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.
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):
|
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 use 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.
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 five 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 overridden 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
// 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)
<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 are 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 CSResource’s 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() |
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) |
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 retrieving 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 below table 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 considered 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¶
In 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 when it comes 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:
-
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.
When directly executed (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()
http://my.server/otcs/cs.exe?func=ll&objId=12345&objAction=Execute&nexturl
=.. orhttp://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 outputThe 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. -
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.
-
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