On almost every topic
Persisting Alfresco Renditions

This post is a follow-up to the post Using Alfresco Composite Rendition to Render PDF. In this tutorial we will update the Java code in order to persist the composite rendition that creates a PDF rendition using Apache FOP and XSL-FO as an intermediate format.

Follow Alfresco

When you persist a rendition in Alfresco, the system will update a rendition every time the document properties are updated. You can follow Alfresco’s rendition behaviour by adding the following line to the log4j.properties file:

log4j.logger.org.alfresco.repo.rendition=debug

Persist a rendition

To make a rendition persistent you need to store the rendition definition using the folowing method:

renditionService.saveRenditionDefinition(compositeDefinition);

In addition to adding this line, I also decided to check if the rendition definition already exists, since you call this code every time you add a new rendition to a document.

Updated code

In the following class the updates are highlighted:

package com.someco.action;

import java.util.List;

import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.rendition.executer.ReformatRenderingEngine;
import org.alfresco.repo.rendition.executer.XSLTRenderingEngine;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;

public class FoRenditionActionExecuter extends ActionExecuterAbstractBase {

  public static final String NAME = "fo";

  public static final String PARAM_TEMPLATE_REF = "template";

  RenditionService renditionService;

  public RenditionService getRenditionService() {
    return renditionService;
  }

  public void setRenditionService(RenditionService renditionService) {
    this.renditionService = renditionService;
  }

  @Override
  protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {

    QName compRenderingName = QName.createQName(
        NamespaceService.CONTENT_MODEL_1_0_URI,
        "compRenderingDefinition");

    CompositeRenditionDefinition compositeDefinition = (CompositeRenditionDefinition) renditionService
        .loadRenditionDefinition(compRenderingName);

    if (compositeDefinition == null) {

      RenditionDefinition xslDefinition = renditionService
          .createRenditionDefinition(QName.createQName(
              NamespaceService.CONTENT_MODEL_1_0_URI,
              "xslRenderingDefinition"), XSLTRenderingEngine.NAME);

      NodeRef template = (NodeRef) action
          .getParameterValue(PARAM_TEMPLATE_REF);

      xslDefinition.setParameterValue(
          XSLTRenderingEngine.PARAM_TEMPLATE_NODE, template);
      xslDefinition.setParameterValue(
          ReformatRenderingEngine.PARAM_MIME_TYPE, "text/xsl");

      RenditionDefinition pdfDefinition = renditionService
          .createRenditionDefinition(QName.createQName(
              NamespaceService.CONTENT_MODEL_1_0_URI,
              "pdfRenderingDefinition"),
              ReformatRenderingEngine.NAME);
      pdfDefinition.setParameterValue(
          ReformatRenderingEngine.PARAM_MIME_TYPE,
          MimetypeMap.MIMETYPE_PDF);

      compositeDefinition = renditionService
          .createCompositeRenditionDefinition(compRenderingName);

      compositeDefinition.addAction(xslDefinition);
      compositeDefinition.addAction(pdfDefinition);

      renditionService.saveRenditionDefinition(compositeDefinition);
    }
    
    renditionService.render(actionedUponNodeRef, compositeDefinition);

  }

  @Override
  protected void addParameterDefinitions(List paramList) {
    paramList.add(new ParameterDefinitionImpl(PARAM_TEMPLATE_REF,
        DataTypeDefinition.NODE_REF, true,
        getParamDisplayLabel(PARAM_TEMPLATE_REF)));
  }

}

Test the rendition

Now if you create a rendition using this custom action, the system will first check if there is already a rendition definition with the same name. If this is not the case, the system persists the rendition definition and creates the rendition.

On any properties update, the system will update the rendition. If you added the log statement to the log properties file, you can trace the rendition engine’s behaviour.

You can check the rendition by locating the document in the node browser. By default the rendition is stored as a hidden child node of the source document.

Using Alfresco Composite Rendition to Render PDF

This is a follow-up to the post Add an XML to PDF Transformer to Alfresco. In this post I looked into the options to add and use an XSL-FO processor using configuration files and some JavaScript. The post ended with the remark that Composite Renditions are not exposed in the JavaScript API. This post provides an example of a custom action that creates a PDF rendition based on XSL-FO.

Note: in order to make these examples work, you need to follow the configuration steps in the previous post. This post assumes you are familiar with customizing Alfresco using Java.

Composite Rendition

A Composite Rendition allows you to create a rendition from one mimetype to another using one or more intermediate transformations. With XSL-FO for example you would normally first transform the XML document into XSL-FO and then transform the result to the desired output format, for example PDF.

Custom Action

To create PDF renditions for XML files using XSL-FO you can write a custom action in Java that creates a Composite Rendition to first transform the XML document into an XSL-FO document and then transforms the result into a PDF. The PDF is then stored as a rendition associated to the XML document.

The following Java class demonstrates this approach. It is a custom action that takes a single parameter to pass the stylesheet:

package com.someco.action;

import java.util.List;

import org.alfresco.repo.action.ParameterDefinitionImpl;
import org.alfresco.repo.action.executer.ActionExecuterAbstractBase;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.rendition.executer.ReformatRenderingEngine;
import org.alfresco.repo.rendition.executer.XSLTRenderingEngine;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ParameterDefinition;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionDefinition;
import org.alfresco.service.cmr.rendition.RenditionService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;

public class FoRenditionActionExecuter extends ActionExecuterAbstractBase {

  public static final String NAME = "fo";

  public static final String PARAM_TEMPLATE_REF = "template";

  RenditionService renditionService;

  @Override
  protected void executeImpl(Action action, NodeRef actionedUponNodeRef) {

    RenditionDefinition xslDefinition = renditionService
      .createRenditionDefinition(QName.createQName(
        NamespaceService.CONTENT_MODEL_1_0_URI,
        "xslRenderingDefinition"), XSLTRenderingEngine.NAME);

    NodeRef template = (NodeRef) action
      .getParameterValue(PARAM_TEMPLATE_REF);

    xslDefinition.setParameterValue(
      XSLTRenderingEngine.PARAM_TEMPLATE_NODE, template);
    xslDefinition.setParameterValue(
      ReformatRenderingEngine.PARAM_MIME_TYPE, "text/xsl");

    RenditionDefinition pdfDefinition = renditionService
      .createRenditionDefinition(QName.createQName(
        NamespaceService.CONTENT_MODEL_1_0_URI,
        "pdfRenderingDefinition"), ReformatRenderingEngine.NAME);
		
    pdfDefinition.setParameterValue(
      ReformatRenderingEngine.PARAM_MIME_TYPE,
      MimetypeMap.MIMETYPE_PDF);

    CompositeRenditionDefinition compositeDefinition = renditionService
      .createCompositeRenditionDefinition(QName.createQName(
        NamespaceService.CONTENT_MODEL_1_0_URI,
        "compRenderingDefinition"));

    compositeDefinition.addAction(xslDefinition);
    compositeDefinition.addAction(pdfDefinition);

    renditionService.render(actionedUponNodeRef, compositeDefinition);
  }

  @Override
  protected void addParameterDefinitions(List paramList) {
    paramList.add(new ParameterDefinitionImpl(PARAM_TEMPLATE_REF,
      DataTypeDefinition.NODE_REF, true,
      getParamDisplayLabel(PARAM_TEMPLATE_REF)));
  }

  public RenditionService getRenditionService() {
		return renditionService;
  }

  public void setRenditionService(RenditionService renditionService) {
		this.renditionService = renditionService;
  }

}

In order to make the custom action available in Alfresco we need to add it as a Spring Bean:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 
  'http://www.springframework.org/dtd/spring-beans.dtd'>
  
<beans>
 
  <bean id="fo" class="com.someco.action.FoRenditionActionExecuter"
    parent="action-executer">
    <property name="publicAction">
      <value>false</value>
    </property>
    <property name="renditionService">
      <ref bean="RenditionService" />
    </property>
  </bean>

</beans>

Execute the action

Next restart Alfresco. You can test the custom action by writing a small JavaScript that executes the action by providing an XML document and an XSL stylesheet that creates XSL-FO output. The FOP distribution provides some examples that you can use. The following code demonstrates how to execute the action using JavaScript:

var fo = actions.create("fo");
fo.parameters.template = companyhome.childByNamePath("/XML/projectteam2fo.xsl");
fo.execute(document);

This code creates a PDF rendition for the given document (in this case a file called projectteam.xml, an example provided by FOP). The rendition is stored as a child of the source document.

Custom Transformer Java Implementation

Once you have started writing Java code, you might as well want to replace the transformer configuration with a Java implementation. The Apache FOP Java library is already on Alfresco’s class path, so a Java implementation does not require a separate install of Apache FOP.

The following class is a basic implementation to provide the custom transformer in Java to replace the transformer defined in the file custom-transformer-context.xml:

package com.someco.transform;

import java.io.BufferedOutputStream;
import java.io.OutputStream;

import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;

import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.AbstractContentTransformer2;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.TransformationOptions;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;

public class FoContentTransformer extends AbstractContentTransformer2 {

  @Override
  public boolean isTransformable(String sourceMimetype,
      String targetMimetype, TransformationOptions options) {
    if (sourceMimetype.equalsIgnoreCase(MimeConstants.MIME_XSL_FO)
        && targetMimetype.equalsIgnoreCase(MimetypeMap.MIMETYPE_PDF))
      return true;
    return false;
  }

  @Override
  protected void transformInternal(ContentReader reader,
      ContentWriter writer, TransformationOptions options)
      throws Exception {

    FopFactory fopFactory = FopFactory.newInstance();

    OutputStream out = new BufferedOutputStream(
        writer.getContentOutputStream());

    try {
      Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, out);

      TransformerFactory factory = TransformerFactory.newInstance();
      Transformer transformer = factory.newTransformer();

      Source src = new StreamSource(reader.getContentInputStream());

      Result res = new SAXResult(fop.getDefaultHandler());

      transformer.transform(src, res);

    } finally {
      out.close();
    }
  }

}

Next update the file custom-transformer-context.xml. Replace the current transformer with a Spring Brean that loads the Java implementation:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 
  'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>

  <bean id="transformer.FOP.PDF"
    class="com.someco.transform.FoContentTransformer"
    parent="baseContentTransformer" />

</beans>

Restart Alfresco and try to run the JavaScript code to create the rendition to PDF.

Read the follow-up post Persisting Alfresco Renditions to learn how to persist renditions to allow Alfresco to update the rendition on any properties update. 

Last updated: October 14, 2011

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.

Paul Siegmann created a Java library for reading and writing EPUB files. I haven’t tested it yet, but if this tool is able to produce valid e-books based on a single XHTML input stream I might be able to replace Calibre with this library in my Alfresco EPUB Publication Module. The code looks interesting enough, so I hope to find some time tomorrow or Friday to run some tests.

Oracle and I.B.M. have agreed to cooperate on Java development. The move comes after Oracle sued Google, claiming its Android operating system illegally used ideas and code from Java.

A positive step towards the future development of the Cobol of the 21st century. What might be even more important is some sort of agreement between Google and Oracle concerning the Android platform. The article states that Google has more developers working on OpenJDK than Oracle that hosts the open source implementation of the Java platform.

Alfresco Custom Actions in JavaScript

For almost every Alfresco project I develop one or more Java Custom Actions to enable the end user to execute specific use cases. Custom actions are directly exposed in the Alfresco Explorer client unless you set the following bean property in the bean configuration for your action:

  <property name="publicAction">
    <value>false</value>
  </property>

If there are additional input parameters required, you can create a dialog or wizard to collect user input.

Since you can also do a lot of things in Alfresco using the JavaScript API, I used to add the custom actions as a root scoped object by creating a class that extends the BaseScopableProcessorExtension class, until I recently realized you can execute any custom action in JavaScript using the ScriptAction API. I have been using this approach to send emails from JavaScript, but never realized this worked for any custom action until I had to extend the mail action itself to allow mail attachments. The only thing my colleague needed to do in the JavaScript code was to change the name of the action and add the parameter for the attachment.

Here is the example to send an email using the ScriptAction API taken from the Alfresco JavaScript Wiki page:

// create mail action
var mail = actions.create("mail");

// set the required parameters
mail.parameters.to = "davidc@alfresco.com";
mail.parameters.subject = "Hello from JavaScript";
mail.parameters.from = "davidc@alfresco.com";
mail.parameters.template = root.childByNamePath("Company Home
  /Data Dictionary/Email Templates/notify_user_email.ftl");
mail.parameters.text = "some text, in case template is not found";

// execute action against a document    
mail.execute(doc);

To execute a custom action in JavaScript you need to know the name of the custom action to retrieve an instance of the action from the list of available actions and the names of the parameters, if there are any. You can then execute the action with the given document or space using the execute() method. For the mail action you can for example lookup the parameters in the Alfresco Javadoc for MailActionExecuter.