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 .
I set up the following Behaviour
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:
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.
————-
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.