4.7. Script action

A script action, also known as ECMS action, is basically a Groovy script that can be triggered by user click or events like content addition.

The action can do lots of things like sending an email or notification, transforming a document into PDF, watermarking an image. It is the way to perform some tasks in Content like restoring a node from Trash. You can find such built-in scripts here.

In this introductory example, you write a script that sends emails telling a user about content update in a specific folder.

Overview

An action involves:

Tip

All the stuffs can be done via UI or by an extension. This tutorial covers both but is more focused in an extension. Source code can be found here.

The extension

You need to create a portal extension by following portal extension tutorial. This section only discusses the webapp. Hereunder is its structure:

The file names are self explanatory. You can change the structure except that scripts/ecm-explorer/action path is not variable.

The Groovy script

First, the script must implement CmsScript, and two methods:

import org.exoplatform.services.cms.scripts.CmsScript;

public class SampleScript implements CmsScript {
    public void execute(Object context) throws Exception {
        ...
    }
    public void setParams(String[] arg0) {} //in this example, this method does nothing.
}

Constructor

This does not need a constructor, however if you need some services from the portal container, you should let the CmsScript framework pass them to your script on its creation:

import org.exoplatform.services.jcr.RepositoryService;

import org.exoplatform.services.jcr.ext.app.SessionProviderService;
...
private RepositoryService repositoryService_ ;
private SessionProviderService seProviderService_;
//Constructor
public SendMailAction(RepositoryService repositoryService, SessionProviderService sessionProviderService) {
    repositoryService_ = repositoryService ;
    seProviderService_ = sessionProviderService;
}

By this way, the framework is responsible for assuring the services are available before you get them, and you do not need to worry about some circumstances - like the script is triggered during the startup when a service is not created yet.

Object context

The object context provides you all the information of the action launching, in summary:

Complete the execute(Object context) method, by extracting information you need and sending a message:

  public void execute(Object context) throws Exception {

    Map values = (Map) context;
    String to = (String) values.get("exo:to");
    String subject = (String) values.get("exo:subject");
    String srcWorkspace = (String) values.get("srcWorkspace");
    String srcPath = (String) values.get("srcPath");
    
    if (to == null) {
      LOG.warn("A SendMailAction at " + srcWorkspace + ":" + srcPath + " is canceled because the TO address is not determined");
      return;
    }
    
    Message message = new Message();
    message.setTo(to);
    message.setSubject(subject);
    message.setBody("There is content update in " + srcWorkspace + ":" + srcPath);
    
    try {
      ((MailService) CommonsUtils.getService(MailService.class)).sendMessage(message);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

To quickly explore which keys the context has, you can cast it to a String and print it out.

Script registration

In configuration.xml file, register your script, with attention to the two path parameters.


<external-component-plugins>
    <target-component>org.exoplatform.services.cms.scripts.ScriptService</target-component>
    <component-plugin>
        <name>manage.script.plugin</name>
        <set-method>addScriptPlugin</set-method>
        <type>org.exoplatform.services.cms.scripts.impl.ScriptPlugin</type>
        <description></description>
        <init-params>
            <value-param>
                <name>autoCreateInNewRepository</name>
                <value>true</value>
            </value-param>
            <value-param>
                <name>predefinedScriptsLocation</name>
                <value>war:/conf/ecms-action</value>
            </value-param>
            <object-param>
                <name>predefined.scripts</name>
                <description></description>
                <object type="org.exoplatform.services.cms.impl.ResourceConfig">
                    <field name="resources">
                        <collection type="java.util.ArrayList">
                            <value>
                                <object type="org.exoplatform.services.cms.impl.ResourceConfig$Resource">
                                    <field name="description"><string>Send Mail Script</string></field>
                                    <field name="name"><string>ecm-explorer/action/SendMailScript.groovy</string></field>
                                </object>
                            </value>
                        </collection>
                    </field>
                </object>
            </object-param>
        </init-params>
    </component-plugin>
</external-component-plugins>

To create a script in UI (Content Administration), go to AdvancedScripts.

The action node type

Define your action node type exo:sendMailAction in nodetypes-configuration.xml file.

Basically it extends the built-in type exo:scriptAction, and adds some other properties. The two first properties are the script's path and label.


<nodeTypes xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:mix="http://www.jcp.org/jcr/mix/1.0"
  xmlns:jcr="http://www.jcp.org/jcr/1.0">
    <nodeType name="exo:sendMailAction" isMixin="false" hasOrderableChildNodes="false" primaryItemName="">
        <supertypes>
            <supertype>exo:scriptAction</supertype>
        </supertypes>
        <propertyDefinitions>
            <propertyDefinition name="exo:script" 
                requiredType="String" autoCreated="true" mandatory="true" onParentVersion="COPY" protected="false" multiple="false">
                <valueConstraints />
                <defaultValues>
                    <defaultValue>ecm-explorer/action/SendMailScript.groovy</defaultValue>
                </defaultValues>
            </propertyDefinition>
            <propertyDefinition name="exo:scriptLabel"
                requiredType="String" autoCreated="true" mandatory="true" onParentVersion="COPY" protected="false" multiple="false">
                <valueConstraints />
                <defaultValues>
                    <defaultValue>Send Mail Action</defaultValue>
                </defaultValues>
            </propertyDefinition>
            ...
        </propertyDefinitions>
    </nodeType>
</nodeTypes>

You want "To" address and the mail subject passed to your script in context parameter, so here you define them as action properties:


<propertyDefinition name="exo:to" 
        requiredType="String" autoCreated="false" mandatory="true" onParentVersion="COPY" protected="false" multiple="false">
    <valueConstraints />
</propertyDefinition>
<propertyDefinition name="exo:subject"
    requiredType="String" autoCreated="true" mandatory="true"
    onParentVersion="COPY" protected="false" multiple="false">
    <valueConstraints />
    <defaultValues>
        <defaultValue>Content update</defaultValue>
    </defaultValues>
</propertyDefinition>

Then register the node type in configuration.xml file:


<external-component-plugins>
    <target-component>org.exoplatform.services.jcr.RepositoryService</target-component>
    <component-plugin>
        <name>add.nodeType</name>
        <set-method>addPlugin</set-method>
        <type>org.exoplatform.services.jcr.impl.AddNodeTypePlugin</type>
        <init-params>
            <values-param>
                <name>autoCreatedInNewRepository</name>
                <description>Node types configuration file</description>
                <value>war:/conf/nodetypes-configuration.xml</value>
            </values-param>
        </init-params>
    </component-plugin>
</external-component-plugins>

In UI (Content Administration), to create an action node type, go to RepositoryNode Types. The UI will give you all the choices as equivalent as an xml definition does. Alternatively, if you want a simple type that extends exo:scriptAction with some string variables, go to AdvancedActions.

The templates

There are properties that you fix the values in the node type definition, and there are properties that you let the users input when they create an action instance. In this example, you let the users choose lifecycle phases (events to trigger the action), "To" and "Subject".

For that purpose, you write two Groovy templates: a dialog to edit the action, and a view to display it.

The dialog, lifecyle selectbox, isDeep checkbox and text fields

See the full code of the dialog SendMailActionDialog.gtmpl in the project source. In almost cases, the dialog allows users to input an action name, so let's start with this structure:

<div class="uiAddActionForm resizable">
  <h6 class="titleBar"><%=_ctx.appRes(uicomponent.getId() + ".title")%></h6>
  <% uiform.begin() %>  
  <div class="form-horizontal" style="min-width:550px;">
    <div class="control-group" style="display:none">
      <label class="control-label" for="id"><%=_ctx.appRes("ScriptAction.dialog.label.id")%>:</label>
      <div class="controls">
        <%
          String[] fieldId = ["jcrPath=/node", "mixintype=mix:affectedNodeTypes", "editable=false", "visible=if-not-null"];
          uicomponent.addMixinField("id", fieldId) ;  
        %>
      </div>
    </div>
    <div class="control-group">
      <label class="control-label" for="actionName"><%=_ctx.appRes("ScriptAction.dialog.label.name")%>:</label>
      <div class="controls">
        <% 
          String[] fieldName = ["jcrPath=/node/exo:name", "validate=empty,XSSValidator"];
          uicomponent.addTextField("actionName", _ctx.appRes("ScriptAction.dialog.label.name"), fieldName);
        %>
      </div>
    </div>
  </div>
  <%uiform.end()%>
</div>
  
<%/* start render action*/%>
  <%uiform.processRenderAction()%>
<%/* end render action*/%>

Then for each variable you add a control-group that consists of a label and an input. The input can vary in many kinds. Though it might not always be the right logic to let users input these fields in this action, the project aims at showing some typical controls, then you see in the code: lifecycle phase selectbox, "isDeep" selectbox, "To" and "Subject" text.

Here is the snippet of lifecycle selectbox:

    <div class="control-group">
      <label class="control-label" for="lifecycle"><%=_ctx.appRes("ScriptAction.dialog.label.lifecycle")%>:</label>
      <div class="controls">
      <% 
        String[] fieldLifecycle = ["jcrPath=/node/exo:lifecyclePhase", 
                                   "options=read,node_added,node_removed,property_added,property_removed,property_changed",
                                   "multiValues=true", "onchange=true","size=5","validate=empty"] ;
        uicomponent.addSelectBoxField("lifecycle", fieldLifecycle) ;
      %>  
      </div>
    </div>

Tip

Add read if you want the ability to run the action from the context menu.

The "isDeep" option, if true, sets the listeners on all over the subtree, so events that happen in child nodes trigger the action.

The view

Much more simpler than the dialog, in a view you just need to get the properties, convert to string if necessary and display them in a table.

<%
  def node = uicomponent.getNode();
  StringBuilder builder;
%>
<table class="uiGrid table table-hover table-striped">            
  <tr>
    <td>Action Name</td>
    <td>
      <%if(node.hasProperty("exo:name")) {%>
        <%=node.getProperty("exo:name").getString()%>
      <%}%>
    </td>
  </tr>
  <tr>
    <td>Lifecycle Phases</td>
    <td>
      <% 
        if (node.hasProperty("exo:lifecyclePhase")) {
          builder = new StringBuilder();
          def values = node.getProperty("exo:lifecyclePhase").getValues();
          for (value in values) {
            builder.append(value.getString()).append(",");
          }
          if (builder.length() > 0) {%><%= builder.deleteCharAt(builder.length() -1) %><%}
        }
      %>
    </td>
  </tr>
  <tr>
    <td>Is Deep?</td>
    <td>
    <%if(node.hasProperty("exo:isDeep")){%>
      <%=node.getProperty("exo:isDeep").getString()%>
    <%}%> 
    </td>
  </tr>
  <tr>
    <td>To Address</td>
    <td>
    <%if(node.hasProperty("exo:to")){%>
      <%=node.getProperty("exo:to").getString()%>
    <%}%> 
    </td>
  </tr>
  <tr>
    <td>Signature</td>
    <td>
    <%if(node.hasProperty("exo:subject")){%>
      <%=node.getProperty("exo:subject").getString()%>
    <%}%> 
    </td>
  </tr>
</table>

Templates registration

To register the view and dialog, add the following to configuration.xml file:


<external-component-plugins>
    <target-component>org.exoplatform.services.cms.templates.TemplateService</target-component>
    <component-plugin>
        <name>addTemplates</name>
        <set-method>addTemplates</set-method>
        <type>org.exoplatform.services.cms.templates.impl.TemplatePlugin</type>
        <init-params>
            <value-param>
                <name>autoCreateInNewRepository</name>
                <value>true</value>
            </value-param>
            <value-param>
                <name>storedLocation</name>
                <value>war:/conf/ecms-action/templates</value>
            </value-param>
            <object-param>
                <name>template.configuration</name>
                <object type="org.exoplatform.services.cms.templates.impl.TemplateConfig">
                    <field name="nodeTypes">
                        <collection type="java.util.ArrayList">
                            <value>
                                <object type="org.exoplatform.services.cms.templates.impl.TemplateConfig$NodeType">
                                    <field name="nodetypeName"><string>exo:sendMailAction</string></field>
                                    <field name="documentTemplate"><boolean>false</boolean></field>
                                    <field name="label"><string>Send Mail Action</string></field>
                                    <field name="referencedView">
                                        <collection type="java.util.ArrayList">
                                            <value>
                                                <object type="org.exoplatform.services.cms.templates.impl.TemplateConfig$Template">
                                                    <field name="templateFile"><string>/views/SendMailActionView.gtmpl</string></field>
                                                    <field name="roles"><string>*</string></field>
                                                </object>
                                            </value>
                                        </collection>
                                    </field>
                                    <field name="referencedDialog">
                                        <collection type="java.util.ArrayList">
                                            <value>
                                                <object type="org.exoplatform.services.cms.templates.impl.TemplateConfig$Template">
                                                    <field name="templateFile"><string>/dialogs/SendMailActionDialog.gtmpl</string></field>
                                                    <field name="roles"><string>*</string></field>
                                                </object>
                                            </value>
                                        </collection>
                                    </field>
                                </object>
                            </value>
                        </collection>
                    </field>
                </object>
            </object-param>
        </init-params>
    </component-plugin>
</external-component-plugins>

Notice the parameter documentTemplate is set to false to mark this as action template.

In UI (Content Administration), to create action templates, go to TemplatesDocuments and select the Actions tab.

Action creation

In UI, to create an action instance for a folder, the user will choose the folder, then select Actions from the Actions bar. Select exo:sendMailAction, then complete the dialog.

If you know a certain location where an instance should be created, you can do it by the extension. Add the following to configuration.xml:


<external-component-plugins>
    <target-component>org.exoplatform.services.cms.actions.ActionServiceContainer</target-component>
    <component-plugin>
        <name>exo:scriptAction</name>
        <set-method>addPlugin</set-method>
        <type>org.exoplatform.services.cms.actions.impl.ScriptActionPlugin</type>
        <init-params>
            <object-param>
                <name>predefined.actions</name>
                <description></description>
                <object type="org.exoplatform.services.cms.actions.impl.ActionConfig">
                    <field name="workspace"><string>collaboration</string></field>
                    <field name="actions">
                        <collection type="java.util.ArrayList">
                            <value>
                                <object type="org.exoplatform.services.cms.actions.impl.ActionConfig$Action">
                                    <field name="type"><string>exo:sendMailAction</string></field>
                                    <field name="name"><string>sendMailAction</string></field>
                                    <field name="description"><string>A sample ECMS action</string></field>
                                    <field name="srcWorkspace"><string>collaboration</string></field>
                                    <field name="srcPath"><string>/sites/shared</string></field>
                                    <field name="isDeep"><boolean>true</boolean></field>
                                    <field name="lifecyclePhase">
                                        <collection type="java.util.ArrayList">
                                            <value><string>node_added</string></value>
                                            <value><string>node_removed</string></value>
                                        </collection>
                                    </field>
                                    <field name="variables">
                                        <string>exo:subject=Content update;exo:to=test@example.com</string>
                                    </field>
                                </object>
                            </value>
                        </collection>
                    </field>
                </object>
            </object-param>
        </init-params>
    </component-plugin>
</external-component-plugins>

Note that:

Some tips for debugging

Sites Explorer or Crash addon sets your sight inside JCR repository. If you are using Sites Explorer, you should change the preferences to enable DMS structure and show hidden nodes. To use Crash, follow Crash addon guide.

Scripts and templates are stored in dms-system workspace, at the paths:

Action instances are stored under the source node. For example, if you have created an action in collaboration:/sites/test-send-mail-action, and name it mailMe, then check the path:

/sites/test-send-mail-action/exo:actions/mailMe                                                                        
+-properties                                                                                                           
| +-jcr:primaryType: exo:sendMailAction                                                                                
| +-jcr:mixinTypes: [exo:sortable,exo:modify,exo:datetime,exo:owneable,mix:affectedNodeTypes,mix:referenceable,exo:rss-
| | enable,mix:lockable]                                                                                               
| +-jcr:uuid: 'fd0a7e497f00010114a120a432b05e7d'                                                                       
| +-exo:dateCreated: 2015-08-05T15:46:40.202+07:00                                                                     
| +-exo:dateModified: 2015-08-05T15:46:40.202+07:00                                                                    
| +-exo:index: 1000                                                                                                    
| +-exo:isDeep: true                                                                                                   
| +-exo:lastModifiedDate: 2015-08-05T15:46:40.204+07:00                                                                
| +-exo:lastModifier: 'root'                                                                                           
| +-exo:lifecyclePhase: ['node_added','node_removed','read']                                                           
| +-exo:name: 'mailMe'                                                                                                 
| +-exo:owner: 'root'                                                                                                  
| +-exo:roles: ['*:/platform/users']                                                                                   
| +-exo:script: 'ecm-explorer/action/SendMailScript.groovy'                                                            
| +-exo:scriptLabel: 'Send Mail Action'                                                                                
| +-exo:subject: 'ContEnt updAte'                                                                                      
| +-exo:title: 'mailMe'                                                                                                
| +-exo:to: 'chautn@exoplatform.com'                                                                                   
+-children
Copyright ©. All rights reserved. eXo Platform SAS
blog comments powered byDisqus