in Alfresco

Alfresco creating custom behaviour – tutorial

Introduction

Previously we talked about alfresco modules and how to create one, deploy it and create test. We recommend you to read it first and get familiar with this way of development.

In this tutorial we are going to explain how to create custom behaviour, that works automatically when node is created or deleted. Example that we are going to explain consists of two types

  • InvoiceFolder
  • Invoice

Invoice among other properties has invoice value for instance $1500. InvoiceFolder contains number of Invoices, one of the properties is named total, that represents sum of all invoices values that are in it.

When invoice is added or deleted parent folder is checked and total value is modified.

Question imposes itself why not do this using rules? Answer is simple, because of maintenance problem, every time we create new InvoiceFolder administrator must add new rule. This way administrator does not have to do anything as it works magically.

Model

Model for this tutorial has ab:invoiceFolder with only one additional property ab:total and ab:invoiceType with properties

  • ab:invoiceDate
  • ab:invoiceNumber
  • ab:invoiceValue

properties are self explanatory so we will not spend to much time on it.

 <type name="ab:invoiceFolder">
   <title>Invoice Folder</title>
   <parent>cm:folder</parent>
   <properties>
    <property name="ab:total">
     <type>d:double</type>
     <mandatory>true</mandatory>
    </property>
   </properties>
 </type>
<type name="ab:invoiceType">
 <title>Invoice Type</title>
 <parent>cm:folder</parent>
 <properties>
  <property name="ab:invoiceDate">
   <type>d:datetime</type>
   <mandatory>true</mandatory>
  </property>
  <property name="ab:invoiceNumber">
   <type>d:int</type>
   <mandatory>true</mandatory>
  </property>
  <property name="ab:invoicevalue">
   <type>d:double</type>
   <mandatory>true</mandatory>
  </property>
 </properties>
</type>

Every time we create a alfresco model we must create java class that represents it, this way our JAVA code is cleaner and easy to read. Lets dive into this class now.

public class AlfrescoBlogModel {

 public static final String APP_MODEL_1_0_URI = "http://www.alfrescoblog.com/model/data/1.0";
 public static final String APP_SHORT = "ab";
/**
 * Invoice Type
 */
 public static final QName TYPE_INVOICETYPE = QName.createQName(
 APP_MODEL_1_0_URI, "invoiceType");
 public static final QName PROP_INVOICE_TYPE_DATE = QName.createQName(
 APP_MODEL_1_0_URI, "invoiceDate");
 public static final QName PROP_INVOICE_TYPE_NUMBER = QName.createQName(
 APP_MODEL_1_0_URI, "invoiceNumber");
 public static final QName PROP_INVOICE_TYPE_VALUE = QName.createQName(
 APP_MODEL_1_0_URI, "invoicevalue");

 /**
 * Invoice folder
 */
 public static final QName TYPE_INVOICEFOLDER = QName.createQName(
 APP_MODEL_1_0_URI, "invoiceFolder");
 public static final QName PROP_TOTAL = QName.createQName(APP_MODEL_1_0_URI,
 "total");
}

Alfresco behaviours

Alfresco has plenty of policies where you can bind your behaviours, policies can be split into few groups listed

  • CheckOutCheckInServicePolicies
    • BeforeCancelCheckOut
    • BeforeCheckIn
    • BeforeCheckOut
    • OnCancelCheckOut
    • OnCheckIn
    • OnCheckOut
  • ContentServicePolicies
    • onContentPropertyUpdate
    • onContentRead
    • onContentUpdate
  • CopyServicePolicies
    • beforeCopy
    • onCopyComplete
    • onCopyNode
  • LockServicePolicies
    • beforeLock
  • NodeServicePolicies
    • beforeAddAspect
    • beforeArchiveNode
    • beforeCreateNode
    • beforeCreateStore
    • beforeDeleteAssociation
    • beforeDeleteChildAssociation
    • beforeDeleteNode
    • beforeMoveNode
    • beforeRemoveAspect
    • beforeSetNodeType
    • beforeUpdateNode
    • onAddAspect
    • onCreateAssociation
    • onCreateChildAssociation
    • onCreateNode
    • onCreateStore
    • onDeleteAssociation
    • onDeleteChildAssociation
    • onDeleteNode
    • onMoveNode
    • onRemoveAspect
    • onSetNodeType
    • onUpdateNode
    • onUpdateProperties
  • TransferServicePolicies
    • beforeStartInboundTransfer
    • onStartInboundTransfer
    • onEndInboundTransfer
  • VersionServicePolicies
    • beforeCreateVersion
    • afterCreateVersion
    • onCreateVersion
    • getRevertVersionCallback
    • afterVersionRevert

We are going to use only two policies OnCreateNodePolicy and  OnDeleteNodePolicy, but code demonstrated here can be easily used for your own usecase.

Spring

For this to work we must define one spring bean, and specify init-method. This method will be called and policies will be created and binded after the bean is initialized. Lets see relevant xml that is a part of service-context.xml .

<bean id="behaviour_invoice"
 class="com.alfrescoblog.InvoiceBehaviour " init-method="init">
   <property name="nodeService">
    <ref bean="NodeService" />
   </property>
   <property name="policyComponent">
    <ref bean="policyComponent" />
  </property>
 </bean>

Implementation of InvoiceBehaviour Spring Bean

We are going to explain part by part of our Spring Bean here

public class InvoiceBehaviour implements
 NodeServicePolicies.OnDeleteNodePolicy,
 NodeServicePolicies.OnCreateNodePolicy {

 // Dependencies
 private NodeService nodeService;
 private PolicyComponent policyComponent;

 public void setNodeService(NodeService nodeService) {
 this.nodeService = nodeService;
 }

 public void setPolicyComponent(PolicyComponent policyComponent) {
 this.policyComponent = policyComponent;
 }

InvoiceBehaviour implements two interfaces OnDeleteNodePolicy and OnCreateNodePolicy. This two interfaces will add two methods onDeleteNode and onCreateNode. We will implement this two methods adding code below.

 @Override
 public void onCreateNode(ChildAssociationRef childAssocRef) {
    NodeRef parent = childAssocRef.getParentRef();
    nodeService.setProperty(parent, AlfrescoBlogModel.PROP_TOTAL,
    getSum(parent));

 }

 @Override
 public void onDeleteNode(ChildAssociationRef childAssocRef,
 boolean isNodeArchived) {
    NodeRef parent = childAssocRef.getParentRef();
    nodeService.setProperty(parent, AlfrescoBlogModel.PROP_TOTAL,
    getSum(parent));
 }

Both methods are similar, when ever invoice is added or deleted recalculate the total value of all invoices that are in this InvoiceFolder using method

getSum(NodeRef parent);

Calculate the Invoice sum

Calculating invoice sum is simple, go through all of the invoices and sum its value. This was done using code below.

 private Double getSum(NodeRef parent) {
 Double sum = 0.0;

    List<ChildAssociationRef> childrenList = nodeService
     .getChildAssocs(parent);
   for (int i = 0; i < childrenList.size(); i++) {
     NodeRef child = childrenList.get(i).getChildRef();
     sum += (Double) nodeService.getProperty(child,
     AlfrescoBlogModel.PROP_INVOICE_TYPE_VALUE);
   }
  return sum;
 }

Remember the part when we create class that represents our model, as you can see this comes handy now .

nodeService.getProperty(child,
     AlfrescoBlogModel.PROP_INVOICE_TYPE_VALUE);

Binding the behaviours

 // Behaviours
 private Behaviour onCreateNode;
 private Behaviour onDeleteNode;

 public void init() {
   this.onCreateNode = new JavaBehaviour(this, "onCreateNode",
   NotificationFrequency.TRANSACTION_COMMIT);

   this.onDeleteNode = new JavaBehaviour(this, "onDeleteNode",
   NotificationFrequency.TRANSACTION_COMMIT);

   this.policyComponent.bindClassBehaviour(OnCreateNodePolicy.QNAME,
   AlfrescoBlogModel.TYPE_INVOICETYPE, this.onCreateNode);

   this.policyComponent.bindClassBehaviour(OnDeleteNodePolicy.QNAME,
   AlfrescoBlogModel.TYPE_INVOICETYPE, this.onDeleteNode);
}

NotificationFrequency is specified when each of JavaBehaviour is created. Possible values are :

  • EveryEvent
  • FirstEvent
  • TransactinCommit

Also there are several binding methods depending of what are you binding to

  • bindAssociationBehaviour
  • bindPropertyBehaviour
  • bindClassBehaviour

Alfresco jUnit test

To prove that this works, we need to create a jUnit test. Test will have two parts.

In the first create one invoiceFolder, and then create two invoices with values 150.0 and 170.0 that are located in this folder. Test InvoiceFolder.total property to be 320.0.

Second part will delete one of the invoices and check if total is 150.0.

Key part of this jUnit Test is listed below .

@RunWith(RemoteTestRunner.class)
@Remote(runnerClass = SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:alfresco/application-context.xml")
public class AlfrescoBlogTest {
 
 private static final String ADMIN_USER_NAME = "admin";
 
 @Autowired
 protected DemoComponent demoComponent;

 @Autowired
 @Qualifier("NodeService")
 protected NodeService nodeService;

 @Test
 public void testWiring() {
   assertNotNull(demoComponent);
 }
 @Test
 public void testInvoiceFolder(){
  AuthenticationUtil.setFullyAuthenticatedUser(ADMIN_USER_NAME);
 
  ChildAssociationRef invoiceFolderChildRef = createInvoiceFolder();
  NodeRef getInvoiceFolder = invoiceFolderChildRef.getChildRef();
  assertNotNull(getInvoiceFolder);
 
  ChildAssociationRef invoice1 = createInvoice(1, 150.0, getInvoiceFolder);
  ChildAssociationRef invoice2 = createInvoice(2, 170.0, getInvoiceFolder);
  assertNotNull(invoice1);
  assertNotNull(invoice2);
 
 Double total =(Double) nodeService.getProperty(
             getInvoiceFolder, AlfrescoBlogModel.PROP_TOTAL)
 Double test = 320.0;
 assertEquals(test, total);
 
 nodeService.deleteNode(invoice2.getChildRef());
 Double total2 =(Double) nodeService.getProperty(getInvoiceFolder, AlfrescoBlogModel.PROP_TOTAL);
 Double test2 = 150.0;
 assertEquals(test2, total2);
}
}

Summary

This concludes our tutorial on how to create custom behaviours in alfresco , how to test them if they work . We recommend you to read our tutorial on how to develop alfresco modules using alfresco maven SDK .

Don't be shellfish...Tweet about this on TwitterShare on LinkedInShare on Google+Share on RedditShare on Facebook

Was this helpful ?

  1. I set up the following Behaviour

    public void init()
    {
    // Create behaviours
    this.onUpdateProperties = new JavaBehaviour(this, “onUpdateProperties”, NotificationFrequency.TRANSACTION_COMMIT);
    // Bind behaviours to node policies
    this.policyComponent.bindPropertyBehaviour(NodeServicePolicies.OnUpdatePropertiesPolicy.QNAME, ContentModel.ASPECT_TITLED, this.onUpdateProperties);
    }
    
    @Override
    public void onUpdateProperties(NodeRef nodeRef, Map before, Map after)
    {
    
    }
    
    

    And the method this.onUpdateProperties is never called when any property of the ASPECT_TITLED is modified. Is there something that I am doing incorrectly?

    • Hello Resco,
      we tested your case and got that it is working like that. here is the code that works for us:

      //definition
      private Behaviour onUpdatePropertiesPolicy;
      
      	public void init() {
      ...
      	this.onUpdatePropertiesPolicy = new JavaBehaviour(this,
      				"onUpdateProperties", NotificationFrequency.EVERY_EVENT);
      
      ...
      	this.policyComponent.bindClassBehaviour(OnUpdatePropertiesPolicy.QNAME,
      				ContentModel.ASPECT_TITLED, this.onUpdatePropertiesPolicy);
      
      ...
      }
      //method that is called on update
            @Override
      	public void onUpdateProperties(NodeRef nodeRef,
      			Map before, Map after) {
      
      		LOGGER.debug("CHANGED " + nodeRef.toString());
      		printProperties(before);
      		printProperties(after);
      
      	}
      
      //method for printing properties
      	public void printProperties(Map map) {
      		Iterator keys = map.keySet().iterator();
      
      		while (keys.hasNext()) {
      			QName key = keys.next();
      
      			LOGGER.debug("CHANGED " + key.getLocalName() + " "
      					+ map.get(key));
      		}
      	}
      
      

      Let us know if you have any other problems.

      • “this.policyComponent.bindClassBehaviour(OnUpdatePropertiesPolicy.QNAME,
        ContentModel.ASPECT_TITLED, this.onUpdatePropertiesPolicy); ”

        In this case this.onUpdateProperties called when any property of the node is modified but I want to call this.onUpdateProperties when modified only property title, description.

        • Yes,
          we have create a jUnit test for this where we update prop_title and prop_author, prop_author is not a part of aspect titled and as a result we do not have onUpdateProperties method called second time.

          		nodeService.setProperty(invoiceFolder, ContentModel.PROP_TITLE,
          				"new title test");
          
          		nodeService.setProperty(invoiceFolder, ContentModel.PROP_AUTHOR,
          				"Ares");
          

          ————-
          Only once onUpdateProperties method is called, we are 100% certain of this. You can send us your code to test in our enviroment to see what is going on if you like. You have our email.