On almost every topic
Integrating the dojo.gantt widget with Alfresco using CMIS

Last week I received an e-mail from an Alfresco developer with a question about integrating dojox.gantt with Alfresco. The dojox.gantt widget is part of the popular Dojo Toolkit and provides an integrated widget for project and resource management.

I never used the Dojo Toolkit before, but I decided to give it a try and I must say that it is a poweful framework for web application development. In this tutorial I will show you how you can integrate dojox.gantt with Alfresco without much coding thanks to Alfresco’s Web Scripts and CMIS. In the end you will be able to load and save project data from Alfresco into the dojox.gantt widget. I used Alfresco Community 4.0b to test the integration, but it should also work with other versions.

Data Model

The dojox.gantt widget uses a simple JSON format to represent project management data. Here is an example:

The structure consists of one or more project items and each project has zero or more tasks. The first step is to upload a dojox.gantt project file into Alfresco. We will use this file as a source for the project data. The dojox.gantt widget requires at least one project item, otherwise you will not be able to open the context menu to add and update projects and tasks. Create a file called gantt_default.json in Alfresco in the Company Home space and add the following lines:

{"identifier":"id","items":[{"id":1,"name":"Development Project","startdate":"2012-2-11","tasks":[]}]}

Web Script

The next step is to create the Web Script. If you are not familiar with the concept of Web Scripts, I suggest that you read this first. On the file system go to the Alfresco Web Scripts folder to create a new Web Script. I used the following location:

tomcat/shared/classes/alfresco/extension/templates/webscripts

First create a file called dojox-gantt.get.desc.xml and add the following lines:

<webscript>
  <shortname>dojox.gantt</shortname>
  <description>dojox.gantt example</description>
  <url>/samples/gantt</url>
  <authentication>user</authentication>
</webscript>

This file declares the Web Script and the URL to access the service. Next create a file dojox-gantt.get.html.ftl and add the following lines:

<html>
  <head>
    <title>Dojo Gantt test</title>
    <link type="text/css" rel="stylesheet" href="http://yandex.st/dojo/1.7.1/dijit/themes/claro/claro.css">
    <link type="text/css" rel="stylesheet" href="http://yandex.st/dojo/1.7.1/dojox/gantt/resources/gantt.css">
    <script src="http://yandex.st/dojo/1.7.1/dojo/dojo.js" type="text/javascript"></script>
    <script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dojox.gantt.GanttChart");

dojo.addOnLoad(function(){
 
  // put the save code here later

  var ganttChart = new dojox.gantt.GanttChart({
    readOnly: false,
    dataFilePath: "${url.context}/s/cmis/p/gantt_default.json/content", 
    withResource: false
  }, "gantt");
  
  ganttChart.init();  
  ganttChart.loadJSONData();
  
});
    </script>
  </head>
  <body class="claro">
    <div class="ganttContent">
      <div id="gantt">
      </div>
    </div>
  </body>
</html>

This file is the view. It loads the required libraries, initializes the Gantt Chart and then loads the data from the server. This line of code is a CMIS get content request to load a file by providing the path:

${url.context}/s/cmis/p/gantt_default.json/content

Since the file is located in Company Home, Alfresco’s root space, providing the name of the file is enough. The /content part of the URL tells Alfresco to return the contents of the file in stead of the object metadata:

In order to test the Web Script first go to the Web Scripts Home page in Alfresco by visiting http://localhost:8080/alfresco/s/. Click the refresh button to add the new Web Script. When you now visit the page http://localhost:8080/alfresco/s/samples/gantt you should see a page similar to this:

When you move the mouse over the project name Requirements you will see a menu to add new tasks.

Saving Data

The dojox.gantt widget is now able to load data from Alfresco using the dataFilePath parameter, but not able to save it back into the system. To save data back into the system you can set a parameter called saveProgramPath in order to provide the server-side script that is able to persist the data. The widget will POST a request to the provided script. 

In Alfresco you can create another Web Script to process the POST data, but this requires additional server-side coding. CMIS is able to directly write content back to the server using the same URL as used to read the content, but this requires a PUT method. The dojox.gantt widget by default uses the POST method. I will first show how to use the standard POST method and then we will extend dojox.gantt to use the PUT method.

Server-side Solution

First create another description document called gantt.post.desc.xml in the same directory as where you put the Web Script to read the Gantt chart and add the following lines:

<webscript>
  <shortname>dojox.gantt</shortname>
  <description>dojox.gantt save example</description>
  <url>/samples/gantt/{filename}</url>
  <authentication>user</authentication>
</webscript>

Next create a JavaScript controller that reads the data submitted by the dojox.gantt widget and saves it back to Alfresco. Create a file gantt.post.js with the following contents:

var filename = url.extension;

if (filename == undefined || filename.length == 0)
{
   status.code = 400;
   status.message = "Filename not provided.";
   status.redirect = true;
}
else
{
  var data = args["data"];
  
  var document = companyhome.childByNamePath(filename);
  
  if (document == null)
  {
    document = companyhome.createFile(filename) ;
  }
  
  document.content = data;
  document.save();

  model.content = data;
}

The last part of the Web Script is the view. This file simply returns the JSON data received from gthe dojox.gantt widget. Create a file gantt.post.json.ftl with the following line:

${content}

The final part is updating the gantt.get.html.ftl. Add the following parameter to the constructor of dojox.gantt.GanttChart just before the dataFilePath parameter:

saveProgramPath: "${url.context}/s/samples/gantt/gantt_default.json",

The result should look like this:

var ganttChart = new dojox.gantt.GanttChart({
  readOnly: false,
  saveProgramPath: "${url.context}/s/samples/gantt/gantt_default.json",
  dataFilePath: "${url.context}/s/cmis/p/gantt_default.json/content",
  withResource: false 
}, "gantt");

Now refresh the Web Scripts and reload the page. Add some tasks to the Gantt chart and click save to save the contents back to Alfresco. You can now save project data back to Alfresco using the default dojox.gantt save method. 

Client-side Solution

To avoid server-side coding we can rewrite the dojox.gantt save function to use a PUT in stead of a POST. I prefer this approach as it does not require any custom coding on the server. When using CMIS you can use the same URL as defined in the dataFilePath parameter to save data back to the server.

The Dojo Toolkit provides the ability to extend an object. To support the PUT method, we can rewrite the saveJSONData function in order to use PUT in stead of POST. Add the following lines of code to the body of the dojo.addOnLoad function in the file gantt.get.html.ftl:

  dojo.extend(dojox.gantt.GanttChart, {
  saveJSONData: function(fileName){
    var _this = this;
    _this.dataFilePath = (fileName && dojo.trim(fileName).length > 0) ? fileName : this.dataFilePath;
    try {
      var td = dojo.xhrPut({
        url: _this.saveProgramPath,
        putData: dojo.toJson(_this.getJSONData()),
        handle: function(res, ioArgs) {
        if ((dojo._isDocumentOk(ioArgs.xhr))||
          (ioArgs.xhr.status == 405)){
          alert("Successfully! Saved data to " + _this.dataFilePath);
        }else{
          alert("Failed! Saved error");
        }
      }
    });
  } catch (e){
    alert("exception: " + e.message);
  }
  }
});

The function can be found in the file GanttChart.js in the Dojo Toolkit SVN that you can access on the Dojo Toolkit Download page. The only changes required are the replacement of xhrPost with xhrPut and the replacement of the content parameter with a parameter putData with the JSON data as value. The reference documentation for xhrPut is here.

In addition to this change the saveProgramPath needs to be added and since the value is the same as the value of the dataFilePath I added a separate variable saveAndLoadPath for the value:

var saveAndLoadPath = "${url.context}/s/cmis/p/gantt_default.json/content"
  
var ganttChart = new dojox.gantt.GanttChart({
  readOnly: false,
  saveProgramPath: saveAndLoadPath,
  dataFilePath: saveAndLoadPath, 
  withResource: false
}, "gantt");

There is no need to refresh the Web Script as the only changes were made in the template. When you run this Web Script again you should be able to save any changes back to the system.

Conclusion

As you can see there is not that much work involved to load and save data from dojox.gantt to Alfresco. Thanks to CMIS a single call can both read and write data. The only challenge was to rewrite the save function to use PUT rather than POST. You can use dojox.gantt to create project plans using Alfresco, for example by creating a custom action or through a Share dashlet. The Alfresco developer who e-mailed me about the integration will use dojox.gantt to provide reports on top of Alfresco Share Data Lists. You can for example do this by creating a custom rendition that renders a Share Task List into the JSON structure that can be loaded into dojox.gantt. 

Updates

February 19, 2012: added the server-side save method as an example.

Processing CMIS with Freemarker in Alfresco

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

Alfresco Rendition Services

I recently posted three tutorials about transforming XML to PDF using XSL-FO and Alfresco’s Rendition Service. The tutorials do not cover every detail, but they might be helpful to those who are interested in using renditions in Alfresco. Alfresco’s Rendition Service allows you to automatically render content in alternative forms when the content or properties are updated.

  1. Add an XML to PDF Transformer to Alfresco
  2. Using Alfresco Composite Rendition to Render PDF
  3. Persisting Alfresco Renditions
Alfresco Java Based Spring Web Script

This post is a follow-up to jQuery DataTables and Spring Web Scripts that focused on writing a back-end service using light weight scripting techniques. In this post we will replace the JavaScript controller with a controller written in Java. This post is just an example. In most cases you can and should use the JavaScript API. It is recommended to read the previous post before you continue.

Download the sample code

You can download the sample code here. The download contains an Eclipse project as a separate archive file. It requires Eclipse and the Alfresco SDK. You can also use a different editor if you want. The client-side code is also added in the ROOT folder. Add the ROOT folder to the webapps folder of your Alfresco Tomcat distribution and download and add the jQuery DataTables libraries to setup the client.

Import the project

To import the project start Eclipse, select File, Import and then Archive File. Select the archive file simple-search-eclipse.zip and click Finish to import the project. Once the project is imported make sure that you add the project SDK Embedded from the Alfresco SDK.

To do this download the SDK, unpack the files and then in Eclipse select File, Import, then choose Existing Projects into Workspace. In the Import dialog select the samples folder in the Alfresco SDK as root folder, select the SDK Embedded project and click Finish to import the project.

To add the SDK embedded project to the Eclipse project select Project in the menu bar, then Properties and select the Java Build Path. Click Add in the Projects tab to add the project.

What we need to do

In order to use a Java class as the controller for a Web Script, we need to write the Java source file and bind the class to our Web Script using a Spring bean declaration. The Spring bean declaration is an XML configuration file that configures the link between the Web Script and the Java class. 

We also need the Web Script descriptor file simplesearch.get.desc.xml and the view simplesearch.get.json.ftl. The file simplesearch.get.js is no longer needed. It contains the JavaScript controller that will be replaced with the Java class. We start with writing the Java code.

Add the Java class

In the source directory of your project create the following package:

com.someco.alfresco.web.scripts.bean

And add a class SimpleSearch.java as a subclass of:

org.springframework.extensions.webscripts.DeclarativeWebScript

You will end up with the following class definition:

package com.someco.alfresco.web.scripts.bean;

public class SimpleSearch extends DeclarativeWebScript {

}

In order to use Alfresco’s public services we add the ServiceRegistry. This registry provides access to all of Alfresco’s public services like the SearchService or the NodeService. You can also add the different services you need, but since we are required to provide the ServiceRegistry to a method later, we simply add the registry and use that to retrieve the services we need.

package com.someco.alfresco.web.scripts.bean;

public class SimpleSearch extends DeclarativeWebScript {

  protected ServiceRegistry serviceRegistry;

  @Override
  protected Map executeImpl(WebScriptRequest req,
    Status status) {

    return null;
  }

  public ServiceRegistry getServiceRegistry() {
    return serviceRegistry;
  }

  public void setServiceRegistry(ServiceRegistry serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
  }

}

Tip: in Eclipse you can use Source, Generate Getters and Setters… to add the getter and setter methods for the serviceRegistry variable.

We start with setting the required parameters. Add the following lines to the executeImpl method:

String searchTerms = req.getParameter("sSearch");

String displayStartArg = req.getParameter("iDisplayStart");
int displayStart = 0;
try {
  displayStart = new Integer(displayStartArg);
} catch (NumberFormatException e) {
}

String displayLengthArg = req.getParameter("iDisplayLength");
int displayLength = 10;
try {
  displayLength = new Integer(displayLengthArg);
} catch (NumberFormatException e) {
}

return null;

Now that we have the required parameters, we can execute a search if the user provided a search term:

// we will use it to add the TemplateNode items
List list = new ArrayList();

int totalRecords = 0;

ResultSet results = null;

if (searchTerms != null && searchTerms.length() > 0) {

  try {
    // create the query statement
    StringBuilder query = new StringBuilder();
    query.append("TYPE:\"");
    query.append(ContentModel.TYPE_CONTENT);
    query.append("\" AND TEXT:\"");
    query.append(searchTerms);
    query.append("\"");

    // define search parameters
    SearchParameters parameters = new SearchParameters();
    parameters.addStore(Repository.getStoreRef());
    parameters.setLanguage(SearchService.LANGUAGE_LUCENE);
    parameters.setQuery(query.toString());

    // execute the query
    results = serviceRegistry.getSearchService().query(parameters);

    totalRecords = results.length();

    // get the number of items to retrieve for the current page
    int totalPageItems = Math.min(displayLength, totalRecords
        - displayStart);

    // add the nodes to the list for our model
    for (int i = 0; i < totalPageItems; i++) {
      NodeRef node = results.getNodeRef(i + displayStart);

      // the Freemarker model requires TemplateNode objects
      list.add(new TemplateNode(node, serviceRegistry, null));
    }

  } finally {
    // make sure that we close our result set to
    // avoid memory leaks
    if (results != null) {
      results.close();
    }
  }

}

And finally we build the model that will be passed to the view, our Freemarker template:

Map model = new HashMap(7, 1.0f);
model.put("iTotalRecords", totalRecords);
model.put("aaData", list);

return model;

The final Java class looks like this:

package com.someco.alfresco.web.scripts.bean;

public class SimpleSearch extends DeclarativeWebScript {

  protected ServiceRegistry serviceRegistry;

  @Override
  protected Map executeImpl(WebScriptRequest req,
      Status status) {

    String searchTerms = req.getParameter("sSearch");

    String displayStartArg = req.getParameter("iDisplayStart");
    int displayStart = 0;
    try {
      displayStart = new Integer(displayStartArg);
    } catch (NumberFormatException e) {
    }

    String displayLengthArg = req.getParameter("iDisplayLength");
    int displayLength = 10;
    try {
      displayLength = new Integer(displayLengthArg);
    } catch (NumberFormatException e) {
    }

    List list = new ArrayList();

    int totalRecords = 0;

    ResultSet results = null;

    if (searchTerms != null && searchTerms.length() > 0) {

      try {
        StringBuilder query = new StringBuilder();
        query.append("TYPE:\"");
        query.append(ContentModel.TYPE_CONTENT);
        query.append("\" AND TEXT:\"");
        query.append(searchTerms);
        query.append("\"");

        SearchParameters parameters = new SearchParameters();
        parameters.addStore(Repository.getStoreRef());
        parameters.setLanguage(SearchService.LANGUAGE_LUCENE);
        parameters.setQuery(query.toString());

        results = serviceRegistry.getSearchService().query(parameters);
        totalRecords = results.length();

        int totalPageItems = Math.min(displayLength, totalRecords
            - displayStart);

        for (int i = 0; i < totalPageItems; i++) {
          NodeRef node = results.getNodeRef(i + displayStart);
          list.add(new TemplateNode(node, serviceRegistry, null));
        }

      } finally {
        if (results != null) {
          results.close();
        }
      }

    }

    Map model = new HashMap(7, 1.0f);
    model.put("iTotalRecords", totalRecords);
    model.put("aaData", list);

    return model;
  }

  public ServiceRegistry getServiceRegistry() {
    return serviceRegistry;
  }

  public void setServiceRegistry(ServiceRegistry serviceRegistry) {
    this.serviceRegistry = serviceRegistry;
  }

}

I did not add all the import statements. In Eclipse you can organize imports using Source and then Organize Imports (Ctrl+Shift+O).

Add the Spring bean declaration

You can read more about the Spring bean declaration for a Java backed Web Script at Alfresco’s Wiki. Create a file called web-scripts-application-context.xml in the folder config/extension and add the following lines:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN 2.0//EN' 
  'http://www.springframework.org/dtd/spring-beans-2.0.dtd'>
<beans>
  <bean id="webscript.com.someco.samples.simplesearch.get" 
    class="com.someco.alfresco.web.scripts.bean.SimpleSearch"
    parent="webscript">
    <property name="ServiceRegistry" ref="ServiceRegistry" />
  </bean>
</beans>

What is important here is that the bean id follows the pattern below:

id="webscript.[packageId].[serviceId].[httpMethod]"

The packageId is the folder where the web script descriptor is located, the serviceId is the name of the service and the method is for example get or post. Also make sure that you set the parent bean to webscript.

Add the descriptor

Finally we need to add the descriptor and the view to the project. These files are the same files as we used for the previous Spring Web Scripts post with the JavaScript controller. First create the following folder in the project:

config/extension/templates/webscripts/com/someco/samples

Next create a file simplesearch.get.desc.xml and add the following lines to the file:

<webscript>
  <shortname>Simple Search</shortname>
  <description>Simple Search Example</description>
  <url>com/someco/simplesearch</url>
  <authentication>user</authentication>
  <format default="json">argument</format>	
</webscript>

Add the view

Then create a file simplesearch.get.json.ftl and add the following Freemarker code:

<#escape x as jsonUtils.encodeJSONString(x)>
{
  "iTotalRecords": ${iTotalRecords}, 
  "iTotalDisplayRecords": ${iTotalRecords},
  "aaData": [
  <#list aaData as child>
    [
    "${child.name}",
    "${child.properties["cm:title"]!""}",
    "${child.properties["cm:author"]!child.properties["cm:creator"]}",
    "${child.properties["cm:modified"]?string("yyyy-MM-dd")}"
    ]<#if child_has_next>,</#if></#list>
  ] 
}
</#escape>

The last two files are no different from the descriptor and the view from the previous post about Spring Web Scripts. We are now ready to deploy the code.

Deployment

To be able to deploy this code check the build.properties file to make sure that the properties refer to your Alfresco distribution and to the Alfresco SDK. Then return to Eclpse, select Window and then Show View and select Ant. In the Ant View click the plus sign and add the build.xml from the Simplesearch project. Click deploy to deploy the code and restart Alfresco.

The Ant build tool writes the Java library simplesearch.jar to the WEB-INF/lib directory and the Web Script files to WEB-INF/classes/alfresco/extension. Make sure to remove the previous Spring Web Script to avoid a naming conflict.

Login to Alfresco and navigate to the Web Script maintenance page to check if the Simple Search service is a registered Web Script:

http://localhost:8080/alfresco/service/

Reload the client page

When you reload the same client HTML file simplesearch.html as we used for the previous post located in the ROOT project, you should see the same result:

Java Backed Web Script 

The main difference is that we did not add sorting to our server-side code.

Add sorting

You can add sorting by adding the parameters for sorting to the class file like we did for the page length and skipcount. To map the column number to a property in Alfresco you can add a static Map.

In order to sort you need to provide a search parameter with the name of the field and a boolean value for ascending order (true) or descending order (false). The field name must be provided in the so called Clark notation preceded by an at sign (@):

@{http://www.alfresco.org/model/content/1.0}name

The following code snippet shows how you can add the sort order to the list of search parameters:

parameters.addSort("@" + ContentModel.PROP_NAME.toString(), true);

Conclusion

In this post I returned to Spring Web Scripts to show how you can replace a JavaScript controller with a Java based controller. In most cases you should be fine writing the controller using JavaScript, but in cases where you need to execute a task that is not easily done using JavaScript, you can use Java.

Read the previous tutorials

Read the related three posts jQuery DataTables and Spring Web Scripts, jQuery DataTables, CMIS and Alfresco and jQuery DataTables and Alfresco.

Share Extras provides a collection of easy-to-install add-ons for Alfresco Share 3.3 and 3.4, providing dashlets, document actions and more.

Today I created the project for the new Alfresco E-Book module in Alfresco Forge and added the code to source control. I need to do some more testing and still need to add internationalization. I also need to write some documentation, since there is quite some functionality. I also added an Alfresco Explorer dashlet (see photo) providing an overview of the content items that are publishable. In a future release I will add support for Alfresco Share. The custom action provides support for Adobe&#8217;s EpubChecker tool. There is a check box in the metadata section of the dialog to turn validation on or off. The validation results are written to a log file in the same space as where the e-book is created.

Today I created the project for the new Alfresco E-Book module in Alfresco Forge and added the code to source control. I need to do some more testing and still need to add internationalization. I also need to write some documentation, since there is quite some functionality. I also added an Alfresco Explorer dashlet (see photo) providing an overview of the content items that are publishable. In a future release I will add support for Alfresco Share. The custom action provides support for Adobe’s EpubChecker tool. There is a check box in the metadata section of the dialog to turn validation on or off. The validation results are written to a log file in the same space as where the e-book is created.

I am getting closer to uploading the publishing module to Alfresco Forge. In the user interface I added some additional metadata fields and in the custom action I also added the ability to add tags using Alfresco&#8217;s standard Taggable aspect. Besides the additional metadata fields you can also use the standard properties to set the authors, title and description. I did not add a rating since it will be part of Alfresco 3.4 Community and I guess also Enterprise. On the to do list is adding support for the Adobe epub checker and the preflight checker, but I am not sure if I am going to add these in the first release. Performance is an issue I have to look into. The custom action execution is very slow.

I am getting closer to uploading the publishing module to Alfresco Forge. In the user interface I added some additional metadata fields and in the custom action I also added the ability to add tags using Alfresco’s standard Taggable aspect. Besides the additional metadata fields you can also use the standard properties to set the authors, title and description. I did not add a rating since it will be part of Alfresco 3.4 Community and I guess also Enterprise. On the to do list is adding support for the Adobe epub checker and the preflight checker, but I am not sure if I am going to add these in the first release. Performance is an issue I have to look into. The custom action execution is very slow.

Updated interface for the Alfresco module for e-book support that I am currently developing. It&#8217;s now using the Alfresco AJAX based file and folder selectors. I also added the ability to save the configuration using a custom aspect.

Updated interface for the Alfresco module for e-book support that I am currently developing. It’s now using the Alfresco AJAX based file and folder selectors. I also added the ability to save the configuration using a custom aspect.