Monday, April 16, 2012

Alfresco Web Scripts

A few days ago I talked about 'Server side Java Script'. This post describes a technique which is named 'Web Script' and explains for which purpose it could be useful. With Web Scripts Alfresco allows to easily deploy content oriented RESTFul Web Services to the Repository. The logic behind these services is then often realized by using Java Script.

A Web Script contains 4 pieces:
  • A descriptor file
  • A Java Script (or a Java Class)
  • A freemarker template
  • Language bundles
The descriptor describes how (which URL) and who can run the Web Script. The Java Script file works as a kind of Controller by setting a model. The Freemarker template contains the presentation logic and the language bundles are allowing multi language support.

Let's begin by investigating the Desctiptor file in detail. Such an descriptor contains:
  • The short name (shortname)
  • The description (description)
  • The url by including the parameters (url)
  • The default format
  • The authentication method
Here some example code:

<webscript>
  <shortname>Hello World</shortname>
  <description>Greet a user</description>
  <url>/helloworld?to={name?}</url>
  <url>/hello/world?to={name?}</url>
  <format default="html">extension</format>
  <authentication>user</authentication>
</webscript>
 Above you can see a Web Script which has the name 'Hello Word'. It 'Greets a user'. The URL is mapped to '/helloworld', whereby one Parameter 'to' is expected. The default response should be 'html' and a user authentication is required in order to execute the script. The value 'extension' means that the requested format is determined by the extension which is used as part of the URL. So for instance 'world.html' or 'world.txt'. As authentication the following values are possible: 'none', 'guest', 'user' or 'admin. Possible formats are: 'html', 'text', 'xml', 'atom', 'rss' or 'json'. There is an optional parameter which is named 'runas'. This parameter is used to execute the Web Script inside the context of another user's session. To be able to fully use this feature it is required that the Web Script is deployed in a static way. (We will talk about the deployment options later in this article.)

What we need next is to investigate the Java Script file. There are specifc naming conventions. So a Java Script which is accessible via HTTP GET should have an Descriptor and a Java Script file which is named as the following:
  • ${id}.${http method}.desc.xml
  • ${id}.${http method}.js
For instance: helloworld.get.desc.xml and helloworld.get.js .



The Java Script file can contain basic Java Script (or even Java because it is interpreted by a Java based engine) and calls of the Alfresco Java Script API.  Let me give you a short overview about this API. It is even useful if you want to implement Actions or Workflows for the Alfresco System. You can think about this Java Script API like a built in language to access Content. If you know Procedural Languages for Database Systems, then you know what I mean. Indeed, the possibilities those are provided by this API are quite beyond the ones of for instance PL/SQL. So even if you are not a Java Script guy, it should be possible to use the API. There are multiple versios of the API available. This article focuses on the 3.4 one.

A script can import another one:

 <import resource="/Company Home/Data Dictionary/Scripts/library.js">
The example above shows a 'dynamic' import. Which means that the imported script is stored in Alfresco itself. It's also possible to just import from the classpath by using "classpath:${path to js}".

 The API provides some root objects. Those objects are available out of the box.

  • companyhome: The company home root folder
  • userhome: The current user's home folder
  • person: The current user object
  • search: The search API
  • people: The people API
  • actions: The action API
  • logger: A logger
  • session: Provides session specific information
  • classification: The classification API
  • utils: Some helpers
  • groups: To access group authorities
  • stites: The sites API
As you can see the API-s can often be accessed by using root object. Often the result of an API call is a document (Or at least the reference to one). If you use the Java Script API, then this is named 'Script Node'. For instance a 'userhome', 'companyhome', 'document', 'space' (Is äquivalent to a folder in Alfresco) or a 'person' is script node. So let's investigate the API-s a little bit more detailed!

The Script Node API has the following important methods:
  • properties
  • children
  • assocs
  • aspects
  • content
  • id
  • nodeRef
  • name
  • type
  • parents
  • childByNamePath
  • getSite
  For all of you who are not such familar with Alfresco: A node has a specific type. A type defines a set of properties. So Properties can be inherited by creating sub-types. An aspect is a bag of properties which can be assigned to a type independent from the inheritance hierarchy.  A node can also have associations to other nodes. A specific association is the child association (if you delete the parent then the childs will become deleted, too).

The properties attribute is an associative array. You can access it by using 'node.properties.propx' or 'node.properties["propx"]'.

Beyond the Script Node API there is also the "Modify and Creating" API. Here some important methods:
  • createFile
  • createFolder
  • createNode
  • addNode (as child)
  • removeNode (remove child assocs)
  • createAssociation
  • move
  • copy
  • addAspect
Also as an extension of the Script Node API, you can think of the Check-in/out and Versioning API
  • checkout
  • checkin
  • cancelCheckout
  • isVersioned
  • versionHistory
  • getVersion
  • createdDate
  • creator
  • label
  • type
  • descrption
  • nodeRef
  • node
The methods are used the same way as the Script node API ones.

The content of the node was accessed by using the content attribute. This one provides the following API:
  • mimetype
  • encoding
  • size
  • url
  • downloadURL
So a node can also have content of a specific mime type.

The search API allows you to search for specific documents. The following methods are part of the search API:
  • luceneSearch
  • xpathSearch
  • savedSearch
  • findNode (by given node reference)
  • query (by using an object which consists a search method and the search query)
The following query is a CMIS one:

var query = {query: "SELECT e.* FROM ecg:mytype AS e WHERE e.ecg:myprop=" + myvar, language: "cmis-alfresco"};

 The people API allows to handle users those are stored inside Alfresco. Here the methods of it:
  • createPerson
  • deletePerson
  • getGroup
  • createGroup
  • parentGroup
  • getMembers
  • isAdmin
  • changePassword
  • setPassword
 The Actions API is used to create an Action object. Then the actions parameters can be set. Afterwards it is possible to execute the action:

var myaction = create("myaction");
myaction.parameters.myparam = 'myvalue';
myaction.execute;
 The classification API provides the following methods:
  • getAllCategoryNodes
  • getAllClassificationAspects
  • createRootCategory
  • isCategory
  • categoryMembers
  • subCategories
  • createSubCategory
  • removeCategory
This section covered not all available API-s. Further more there are the Permission API, the Transformation API, the Thumbnailing API, the Tagging API, the Workflow API, the AVM API and the WCM API.

A special root object for Web Scripts is the one which is named 'model'. It allows you to set the result of the execution of you web script. Here an example:

var username = person.properties.userName;
model.username = username;
The model object is used to transfer data from the controlling Web Script to the Freemarker template. So let's take a closer look on Freemarker templates!

Basically a freemarker template can be anything which is textual. So you can write it as html, as text as xml. The idea behind it is to use some static text with some dynamic text from the model. There is a whole language about this freemarker stuff. We just look on the most important parts:
  • Place holders
  • Data model
  • Variables and Types
  • Sequences
  • Directives
  • Comments
  • Built-ins
Place holders can be found as ${myPlaceholder} inside the template. Such a placeholder will then become replaced regarding the data from the model.

The data model is a Tree and can be set. Every value which you access via a place holder is stored inside the data model. Everything which is not a leaf inside the data model's tree is named 'Hash'. The variables inside the tree those are storing single values (the leafs of the tree) are named 'Scalars'. You can navigate through the model's tree by using indexes. For instance 'animals[0].name'.  Scalars are typed: String, Number, Date/Time, Boolean .

Sequences are lists of strings:

["myval1", ... , "myvaln"]

There are several directives. The 'if' directive looks as the following:

<#if myvar = "mystringval">  My text <#else> My other text </#if>
Possible operators are: <, >, !=, = .

The list directive is used to itterate over elements of the data model.

<# list myhashvar as myvar> ${myvar.myscalarvar} </#list>
 <# list myseq as myvar> ${myvar} </#list>

The include directive can be used to insert content from another file to the template:

<#include "/myfile.html">
It is possible to create user defined directives the following way:

<@mydirective myparam1=myval1, ... myparamn=myvaln>
    <#--   Some commands --#>
</@mydirective>
 Comments are used the following way:
<#-- My comment --#>
The following built in functions are available:

  • myvar!"My optional value": Uses the optional value if  myvar is not set
  • myvar??: Returns true if myvar is set
  • myvar?upper_case: Returns the variable value in upper case letters
  • myvar?cap_first: Converts the first letter to upper case
  • myvar?trim
  • myvar?size
  • myvar?int: The integer part of a number (E.G. 5 in case of 5.432)
  • myvar?string_short: Date to String conversion
  • myvar?foo:string : Boolean to Strng conversion 
Alfresco extended the capabilities of Freemarker templates a bit, so that you can access some specific API elements directly from the Freemarker Template without the need of a Java Script as the controller. However, I would recommend to stay with the MVC-pattern!

Let's now investigate how multiple languages are supported. The default message bundle is a properties-file with the following name:
  • ${id}.${http method}.properties
 To support multiple languages, you can create a new file which is named:
  • ${id}.${method}_${locale}.properties
The messages are then accessible directly from your Freemarker template via a function call:

 ${msg("myprop")}
So the final question of this article is: How can I deploy and use such a Web Script. There are multiple ways to deploy a web script:
  • Dynamic deployment: To the Content Repository itself. Which mean you need to import it to 'Data Dictionary/Web Scripts' or 'Data Dictionary/Web Script Extensions' (The second folder should be used to override scripts from the first folder for testing purposes.)
  • Static deployment by copying the folder which contains the Web Script files  to ${CATALINA_HOME}/webapps/alfresco/WEB-INF/classes/alfresco/templates/webscripts. This can be simplified by bundling the Web Script into an Alfresco Module Package.
  • Extension deployment by copying the folder which contains the Web Script files to ${CATALINA_HOME}/shared/classes/alfresco/extension/templates/webscripts
Afterwards you have to refresh the list of available Web Scripts. No server restart is required:

  • Navigate to 'http://${myhost}:8080/alfresco/service/index'
  • Click on the 'Refresh Web Scripts' button

1 comment: