Custom Actions

cancel
Showing results for 
Search instead for 
Did you mean: 

Custom Actions

resplin
Intermediate
0 0 9,483

Obsolete Pages{{Obsolete}}

The official documentation is at: http://docs.alfresco.com



Back to Developer Guide


Overview


An action is a unit of work that can be performed against a node.  It is configured through a content rule.  For example, moving a node, copying a node, checking a node in, transfoming the contents of a node, etc.

There are a number of standard action types provided within the Alfresco repository, but it is also possible to add your own custom actions.  This page is intended to give a 'How To' guide for those wanting to create custom actions.


Getting Started


First you will need to decide what you want your action to do.  For the sake of this tutorial I will use the simple example of an action that applies an aspect to node.

I will call this action AddAspect.  The real life implementaion of this action can be found in the repository (org.alfresco.repo.action.executer.AddFeaturesActionExecuter) and is called 'AddFeatures'.


Writing an action executer


An action executer contains the implementation of an action.  It's where you put the code that is going to do the work with the node.

An action executer must implement the interface org.alfresco.repo.action.executer.ActionExecuter.



public interface ActionExecuter
{
   /**
    * Get the action definition for the action
    *
    * @return  the action definition
    */
   public ActionDefinition getActionDefinition();

   /**
    * Execute the action executer
    *
    * @param action    the action
    * @param actionedUponNodeRef the actioned upon node reference
    */
   public void execute(
      Action action,
        NodeRef actionedUponNodeRef);
}

As you can see there are two parts to the contract:


  1. Provide an action definition object for your action. This gives details of the name of the action and details of the parameters that it is expecting.
  2. Provide an execution method. This will take the instance of the action and the node and perform the work specified.

This interface can be extended directly, but there is some additional work you will need to do in order for the action service to recognize the action as available to the client (see Extending The Action Executer Interface Directly for more details), but in most situations it is best to extend from ActionExecuterAbstractBase found in the same package.

The abstract base class, ActionExecuterAbstractBase, does a lot of the extra work for you and provides some built in behavior and a couple of abstract methods to implement making life a bit easier.

Back to our example, if we're going to create the 'AddAspect' action the first thing we're going to do is create a new class extending ActionExecuterAbstractBase.



public class AddAspectActionExecuter extends ActionExecuterAbstractBase
{
   /**
    * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(Action, NodeRef)
    */
   @Override
   public void executeImpl(Action action, NodeRef actionedUponNodeRef)
   {
      // TODO fill in implementation
   }

   /**
    * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
    */
   @Override
   protected void addParameterDefinitions(List<ParameterDefinition> paramList)
   {
      // TODO fill in action parameter definitions
   }
}

We can now start filling in the implementation of our action and to do this we are going to need a couple of things:


  • the details of the aspect we want to apply to the node, and
  • an instance of the node service.

Lets deal with the node service.  Since action executors are intended to be configured in Spring it means getting hold of any of the repository services is a simple matter of adding the setter methods that will be used by the Spring config to inject the required services.



public class AddAspectActionExecuter extends ActionExecuterAbstractBase
{
   /**
    * the node service
    */
   private NodeService nodeService;

   /**
    * Set the node service
    *
    * @param nodeService  the node service
    */
   public void setNodeService(NodeService nodeService)
   {
      this.nodeService = nodeService;
   }

}

Next we need to parametrize the action executer so that we can get hold of the details of the aspect to apply when the action is executed.

When an action is created it is based upon an action definition (which, as we have seen, relates to an action executer).  An instance of an action will contain values for the parameters that the action definition defines.

In this example we want an action of type 'AddAspect' to provide a parameter containing the QName of the aspect to apply.  We specify this by adding a parameter definition in the method addParameterDefintions.



public class AddAspectActionExecuter extends ActionExecuterAbstractBase
{
   public static final String NAME = 'add-aspect';
   public static final String PARAM_ASPECT_NAME = 'aspect-name';

   /**
    * @see org.alfresco.repo.action.ParameterizedItemAbstractBase#addParameterDefinitions(java.util.List)
    */
   @Override
   protected void addParameterDefinitions(List<ParameterDefinition> paramList)
   {
      // Add definitions for action parameters
      paramList.add(
         new ParameterDefinitionImpl(                       // Create a new parameter defintion to add to the list
            PARAM_ASPECT_NAME,                              // The name used to identify the parameter
            DataTypeDefinition.QNAME,                       // The parameter value type
            true,                                           // Indicates whether the parameter is mandatory
            getParamDisplayLabel(PARAM_ASPECT_NAME)));      // The parameters display label
   }
}

Now, when getActionDefinition is called from the base class, an action definition class will be returned containing the correct parameter definitions.  This is taken care of by ParameterizedItemAbstractBase, which is in the inheritance hierarchy. 

With this done we can fill in the implementation of the action executer.



public class AddAspectActionExecuter extends ActionExecuterAbstractBase
{
    /**
    * @see org.alfresco.repo.action.executer.ActionExecuterAbstractBase#executeImpl(Action, NodeRef)
    */
  public void executeImpl(Action action, NodeRef actionedUponNodeRef)
   {
      // Check that the node still exists
      if (this.nodeService.exists(actionedUponNodeRef) == true)
      {
         // Get the qname of the aspect to apply, we know it must have been set since it is mandatory parameter
         QName aspectQName = (QName)action.getParameterValue(PARAM_ASPECT_NAME);
       
         // Use the node service to apply the aspect to the node
         this.nodeService.addAspect(actionedUponNodeRef, aspectQName, null);
      }
   }

}

Internationalization (i18n) of the action


Associated with an action is a title and description.  In addition, each parameter has a displayable title.  All these string should be retrieved from i18n bundles to ensure that the repository remains localisable.

The abstract base class, ActionExecuterAbstractBase, looks somewhere in a loaded bundle, the entries <action-class-name>.title and <action-class-name>.description.

Also the method getParamDisplayLabel is used to provide a message id for a parameter.  It will generate a message id of the form <action-class-name>.<param-name>.display-label.  This message should be used when creating a parameter defintion. (see example above).

In our example this means in order to internationalise our custom action we must add the following lines to a resource bundle that is being loaded.



   # Custom action messages

   add-aspect.title=Add aspect to item
   add-aspect.description=This will add an aspect to the matched item.
   add-aspect.aspect-name.display-label=The name of the aspect to apply to the node.


If you want to learn more about how the respositories' I18N features work, go to Multilingual Document Support.


Configuring in Spring


In order to make our custom action available, we need to configure the action as a Spring bean.  This can be done by adding the following configuration into the classes/alfresco/action-services-context.xml configuration file.



<bean id='add-aspect' class='org.alfresco.repo.action.executer.AddAspectActionExecuter' parent='action-executer'>
    <property name='nodeService'>
        <ref bean='nodeService' />
    </property>
</bean>

Alternatively, equivalent configuration can be added to your own Spring configuration file.

In order to prevent the UI from picking this new action up before we have added the UI to support it we can configure the action to be private.  The action can still be used in the repository but the web client will not present it in the list of available action types.



<bean id='add-aspect' class='org.alfresco.repo.action.executer.AddAspectActionExecuter' parent='action-executer'>
    <property name='nodeService'>
        <ref bean='nodeService' />
    </property>
    <property name='publicAction'>
        <value>false</value>
    </property>
</bean>

Testing the action


When writing a custom action we highly recommend that you write a unit test to accompany it.  The following code shows an example unit test for the add aspect custom action we have been developing.

As you will see we have used the BaseSpringTest class as a basis for our tests, and you will probably find it useful to do so too.




/**
* Add aspect action execution test
*
* @author Roy Wetherall
*/
public class AddAspectActionExecuterTest extends BaseSpringTest
{
    /**
     * The node service
     */
    private NodeService nodeService;
   
    /**
     * The store reference
     */
    private StoreRef testStoreRef;
   
    /**
     * The root node reference
     */
    private NodeRef rootNodeRef;
   
    /**
     * The test node reference
     */
    private NodeRef nodeRef;
   
    /**
     * The add aspect action executer
     */
    private AddAspectActionExecuter executer;
   
    /**
     * Id used to identify the test action created
     */
    private final static String ID = GUID.generate();
   
    /**
     * Called at the begining of all tests
     */
    @Override
    protected void onSetUpInTransaction() throws Exception
    {
        this.nodeService = (NodeService)this.applicationContext.getBean('nodeService');
       
        // Create the store and get the root node
        this.testStoreRef = this.nodeService.createStore(
                StoreRef.PROTOCOL_WORKSPACE, 'Test_'
                        + System.currentTimeMillis());
        this.rootNodeRef = this.nodeService.getRootNode(this.testStoreRef);

        // Create the node used for tests
        this.nodeRef = this.nodeService.createNode(
                this.rootNodeRef,
                ContentModel.ASSOC_CHILDREN,
                QName.createQName('{test}testnode'),
                ContentModel.TYPE_CONTENT).getChildRef();
       
        // Get the executer instance
        this.executer = (AddAspectActionExecuter)this.applicationContext.getBean(AddAspectActionExecuter.NAME);
    }
   
    /**
     * Test execution
     */
    public void testExecution()
    {
        // Check that the node does not have the classifiable aspect
        assertFalse(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE));
       
        // Execute the action
        ActionImpl action = new ActionImpl(ID, AddAspectActionExecuter.NAME, null);
        action.setParameterValue(AddAspectActionExecuter.PARAM_ASPECT_NAME, ContentModel.ASPECT_CLASSIFIABLE);
        this.executer.execute(action, this.nodeRef);
       
        // Check that the node now has the classifiable aspect applied
        assertTrue(this.nodeService.hasAspect(this.nodeRef, ContentModel.ASPECT_CLASSIFIABLE));
    }
}

Related links


Customizing and Extending
Actions