Since its release in version 2.1 Alfresco Web Scripts proved to be extremely useful as a framework to build data oriented services and UI Components. They are even used to develop complete application integrations. Thanks to Web Scripts Alfresco was for example able to deliver one of the first CMIS implementations.
Officially Web Scrips are now part of the Spring Surf Framework, but the Spring community never embraced Surf and it will not surprise me if the maintenance and development of the framework returns to Alfresco in the near future. I think the limited tool support also contributed to the lack of success.
I have always been a great fan of Web Scripts. It is a simple yet powerful MVC framework that allows you to build services with just a couple of simple files. You implement the controller using Server Side JavaScript and the view using Freemarker, a very powerful templating language that can output any text oriented data wheter it is HTML, JSON or some XML based format.
Remote Client
In Alfresco Explorer you have direct access to model objects, but with Alfresco Share this is not the case. Alfresco Share is a remote client that requires you to collect data from the Alfresco backend using a service layer. You can write your own backend services with Web Scripts, but you can also use CMIS or one of the other backend services provided out of the box.
The problem with CMIS is that each request returns an XML response and XML and JavaScript are not a great marriage. With the upcoming CMIS 1.1 you can use JSON as an alternative binding. To some extend you can use the Abdera JavaScript client, but Abdera focuses on Atom entries and as far as I know there is no extension that allows easy access to all the CMIS specific properties.
After writing a lot of lines of code in JavaScript (click here for an example from Alfresco’s 3.4 documentation), I got tired of coding and decided to use a more simple approach by consuming the CMIS data using Freemarker. I am aware that it is the responsibility of the controller to deliver an easy to use model to the view, but Freemarker provides proper support for XML including XPath support.
Example Web Script
The following example Web Script shows how you can pass the CMIS response to the view in the controller layer and use the data within the Freemarker template.
The first step is to define a new Web Script in Alfresco Share. Navigate to the ‘tomcat/shared/classes/alfresco/web-extension/site-webscripts’ folder and create the Web Script. A Web Script consists of a Web Script descriptor, an optional JavaScript controller and a Freemarker template for the view. First create a folder ‘samples’ for the example Web Scripts. In this folder create a file called ‘sample-cmis.get.desc.xml’ with the following contents:
<webscript>
<shortname>XML example</shortname>
<description>XML Example</description>
<url>/sample/cmis</url>
</webscript>
The next step is the controller. In the same folder create a file ‘sample-cmis.get.js’ with the following contents:
var connector = remote.connect("alfresco");
var result = connector.get("/cmis/p/sample.txt");
model.doc = stringUtils.parseXMLNodeModel(result.getText());
This script first creates a connection with the Alfresco backend. The connection is then used to execute a CMIS request. The request ‘/cmis/p/sample.txt’ retrieves the object data by path for a file called ‘sample.txt’ located in the Company Home root space. I simply created a plain text file in Alfresco with some dummy content and name, title, author and description properties. The CMIS response is then passed to the Freemarker template as an XML document using the parseXMLNodeModel method available in the root scoped object stringUtils (see here).
In order to retrieve values from the model in Freemarker you can now use the XML document. Create a file called ‘sample-cmis.get.ftl’ and add the following lines:
<#ftl ns_prefixes={ "D":"http://www.w3.org/2005/Atom" }>
<p>${doc.entry.title}</p>
Since CMIS uses namespaces to combine several schema’s, you need to register the prefixes of the namespaces. In the example above only the default ‘atom’ namespace is registered. Freemarker uses a D as a prefix for the default namespace. If you do not register the namespaces, including the default namespace, Freemarker will not retrieve any values and throw an exception.
In order to register the Web Script in Share you can visit http://localhost:8080/share/service/index.html and click the refresh button. If you then visit the page http://localhost:8080/share/page/sample/cmis you should see a page with the name of the document.
Tip: if you set the mode of Share to ‘development’ in ‘surf.xml’ you do not need to refresh the Web Scripts every time you make changes. You can find the file in ‘tomcat/webapps/share/WEB-INF’.
Adding namespaces
In order to, for example, print the icon of the document, we need to add the Alfresco namespace. Freemarker requires the use of square brackets when selecting elements with prefixes because otherwise the colon would confuse the Freemarker engine. The following template outputs the icon and name of the document:
<#ftl ns_prefixes={ "D":"http://www.w3.org/2005/Atom",
"alf":"http://www.alfresco.org" }>
<p><img src="${doc.entry["alf:icon"]}" />${doc.entry.title}</p>
When you revisit the page you should see a page similar to this:

Using XPath
You can also use XPath to select values from the CMIS document. The following template retrieves the Alfresco author property using an XPath expression:
<#ftl ns_prefixes={ "D":"http://www.w3.org/2005/Atom",
"alf":"http://www.alfresco.org",
"cmis":"http://docs.oasis-open.org/ns/cmis/core/200908/" }>
<p><img src="${doc.entry["alf:icon"]}" />${doc.entry.title}</p>
<p>Author: ${doc["//cmis:propertyString[@propertyDefinitionId = 'cm:author']/cmis:value"] </p>
You can parse a date value in order to reformat the date like this:
<p>Updated: ${doc.entry.updated?date("yyyy-MM-dd'T'HH:mm:ss")?string("yyyy-MM-dd HH:mm:ss")}</p>
In order to retrieve the value, a third namespace prefix was registered to select elements from the ‘cmis’ namespace.
Using The List Directive
You can also retrieve multiple propeties using XPath. The following template lists all the CMIS property display labels and values:
<#ftl ns_prefixes={ "D":"http://www.w3.org/2005/Atom",
"alf":"http://www.alfresco.org",
"cmis":"http://docs.oasis-open.org/ns/cmis/core/200908/" }>
<p><img src="${doc.entry["alf:icon"]}" />${doc.entry.title}</p>
<ol>
<#list doc["//cmis:properties/*/cmis:value"] as p>
<li>${p?parent.@displayName}: ${p}</li>
</#list>
</ol>
In order to include the properties of aspects, you can for example use the XPath local-name() function:
<#list doc["//*[local-name() = 'properties']/*/cmis:value"] as p>
<li>${p?parent.@displayName}: ${p}</li>
</#list>
When you revisit the page, the result should look simliar to this:

Building a model
To make things easier to process in your template you can write a function that returns a map of name and propety values. You can then use the map as you are used to in backend Web Scripts. The following function retrieves the properties and adds them to a map using the property name as a key:
<#function properties doc>
<#assign s = "{"> <#list doc["//*[local-name() = 'properties']/*/cmis:value"] as p>
<#assign s = s + "\"${p?parent.@propertyDefinitionId}\":\"${p}\"">
<#if p_has_next> <#assign s = s + ","> </#if> </#list>
<#assign s = s + "}">
<#return s?eval>
</#function>
This is just a basic function that works fine for non-repeatable fields. You can execute the function and use the properties for example in a list directive:
<#assign props = properties(doc)>
<ol>
<#list props?keys as key>
<li>${key}: ${props[key]}</li>
</#list>
</ol>
You can of course also get individual properties by property name. The following line outputs the description of the document:
<p>Description: ${props["cm:description"]}</p>
The following listing shows the complete Freemarker template using the function:
<#ftl ns_prefixes={ "D":"http://www.w3.org/2005/Atom",
"alf":"http://www.alfresco.org",
"cmis":"http://docs.oasis-open.org/ns/cmis/core/200908/" }>
<#function properties doc>
<#assign s = "{">
<#list doc["//*[local-name() = 'properties']/*/cmis:value"] as p>
<#assign s = s + "\"${p?parent.@propertyDefinitionId}\":\"${p}\"">
<#if p_has_next>
<#assign s = s + ",">
</#if>
</#list>
<#assign s = s + "}">
<#return s?eval>
</#function>
<p><img src="${doc.entry["alf:icon"]}" /> ${doc.entry.title}</p>
<#assign props = properties(doc)>
<ol>
<#list props?keys as key>
<li>${key}: ${props[key]}</li>
</#list>
</ol>
When you visit the example page again you will see a listing of all the property names and values:

Conclusion
Although from a design point of view this approach might not be perfect, it provides a means to consume CMIS responses in just a couple of lines of code. If you have other suggestions to consume CMIS documents in Web Scripts, please let me know.
The Learning by Example page from the Freemarker Manual can be a useful resource when processing XML documents with Freemarker.
Updates
- Made some minor updates on January, 26
References
-
web-scripts reblogged this from bpeters
-
bpeters posted this