Recently SeedIM was requested by one of our customers to provide a way within Alfresco to set a folder quota limit. Users were using Alfresco for data backup and that was consuming too much space. While Alfresco allows you to have a quota on how much content a user can add to Alfresco, there is no such mechanism at the folder level. So in this blog we will show you how we implemented this.
Alfresco Folder Quota
The folder quota solution is made up of the following parts;
- An Aspect to hold metadata about the Folder Quota.
- Repository actions to set and remove the Folder Quota metadata.
- Behaviour to keep track of the Folder size, update Folder Quota metadata and prevent users from adding content when the folder limit is reached.
- Override Upload Document webscript to display appropriate error notification if folder quota is being exceeded.
- Share Actions, Form and evaluators for the management folder quota.
- Share Metadata Template and custom renderer to display the folder limit and actual size in the folder browse view
Folder Quota Aspect
We used an aspect to hold the required information about the folder quota which is defined as follows in our content model;
<aspect name="seed:quotaAspect"> <title>Quota Aspect</title> <description>Seed Quota Aspect</description> <properties> <property name="seed:sizeCurrent"> <title>Current Size</title> <description>Seed Current Size</description> <type>d:long</type> <multiple>false</multiple> </property> <property name="seed:sizeLimit"> <title>Size Limit</title> <description>Seed Size Limit</description> <type>d:int</type> <default>50</default> </property> </properties> </aspect> Set Folder Size Limit Repo Action
The Set Folder Size Limit Repo Action is mainly used for the following;
- Applies the Folder Quota Aspect
- Sets the quota limit in seed:sizeLimit property based on the input from the user
- Uses a recursive method (see snippet below) to get the current size of the folder and then sets it in seed:sizeCurrent property
public long getNodeSize(NodeRef nodeRef) { long size = 0; try { if(nodeService.getType(nodeRef).isMatch(ContentModel.TYPE_CONTENT)) { ContentData contentData = (ContentData) nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); size = contentData.getSize(); } } catch (Exception e) { if(logger.isDebugEnabled()) logger.debug("Could not get size for node " + nodeRef + " err: " + e.getMessage()); size = 0; } List<ChildAssociationRef> chilAssocsList = nodeService.getChildAssocs(nodeRef); for (ChildAssociationRef childAssociationRef : chilAssocsList) { NodeRef childNodeRef = childAssociationRef.getChildRef(); size = size + getNodeSize(childNodeRef); } return size; } Remove Folder Size Limit Repo Action
As the name implies the Remove Folder Size Limit Repo Action is used to remove the Folder Quota Aspect. See below code snippet.
protected void removeFolderSizeLimit(final Action action, final NodeRef actionedUponNodeRef) { try { if(nodeService.exists(actionedUponNodeRef) && nodeService.hasAspect(actionedUponNodeRef, SeedModel.ASPECT_QUOTAASPECT)) { behaviourFilter.disableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); nodeService.removeAspect(actionedUponNodeRef, SeedModel.ASPECT_QUOTAASPECT); behaviourFilter.enableBehaviour(actionedUponNodeRef, ContentModel.ASPECT_AUDITABLE); } }catch(Exception e) { throw new AlfrescoRuntimeException("Could not remove Folder Size limit for node " + actionedUponNodeRef + " err: " + e.getMessage()); } } Folder Quota Behaviour
The Folder Quota behaviour is used to track the folder content and implements the following two policies;
BeforeDeleteNodePolicy
Checks if the document being deleted parent or ancestors have the Folder Quota Aspect and if yes then get the document size and update the seed:sizeCurrent property accordingly.
OnContentPropertyUpdatePolicy
Checks if the document being deleted parent or ancestors have the Folder Quota Aspect and if yes then get the document size, cross check if it would exceed the set quota and if yes then throw an AlfrescoRuntime exception.
Upload Content Webscript
Out of the box, whenever an exception is encountered during the upload of a document, a generic notification error is thrown. In order to throw a more relevant error when the Folder Quota is exceeded, the following was added to upload.post.js which is used when a user clicks on the Upload button in a Share.
else if (e.message && e.message.indexOf("org.alfresco.service.cmr.repository.ContentIOException") == 0) { var error = e + ""; if (error.indexOf("Folder Quota Exception") != -1){ var newErr = new Error("Folder Quota Exceeded"); newErr.stack += '\nCaused by: Folder Quota Exceeded'; newErr.code = 413; newErr.message = "Folder Quota Exceeded"; throw newErr; } Share Set Folder Limit Action and form
The Share Set Folder Limit action calls the onActionFormDialog js handler to call the custom form we defined for the user to input the Folder Size limit when the latter clicks on the Set Folder Size Limit action in Share.
The Share Folder Size limit Action definition is as follows;
<action id="seed-setFolderSizeLimit" type="javascript" label="seed.action.setFolderSizeLimit.label" icon="enabled-indicator-off"> <param name="function">onActionFormDialog</param> <param name="itemKind">action</param> <param name="itemId">setFolderSizeLimitAction</param> <param name="mode">create</param> <param name="destination">{node.nodeRef}</param> <param name="successMessage">message.seed.setFolderSizeLimit.successful</param> <param name="failureMessage">message.seed.setFolderSizeLimit.failed</param> <evaluator>seed.evaluator.doclib.action.canSetFolderSizeLimit</evaluator> <evaluator negate="true">seed.evaluator.doclib.action.hasQuotaAspect</evaluator> </action>
The custom form definition is shown below and note that the condition has to match the bean id of the repo action;
<forms> <config evaluator="string-compare" condition="setFolderSizeLimitAction"> <form> <field-visibility> <show id="sizeLimit"/> </field-visibility> <appearance> <field id="sizeLimit" label-id="seed.action.setFolderSizeLimit.form.field.sizeLimit"> <control template="/org/alfresco/components/form/controls/number.ftl" /> </field> </appearance> </form> </forms> </config> Share Custom renderer and Metadata template
In order to display the Folder Size limit and Folder Size Current as shown below, we had to use a share custom renderer and metadata template.
We defined a custom renderer for the current folder size because the default value its always returned in bytes.
YAHOO.Bubbling.fire("registerRenderer", { propertyName: "seedFolderSizeCurrentCustomRendition", renderer: function seedFolderSizeCurrent_renderer(record, label) { var jsNode = record.jsNode, properties = jsNode.properties, html = ""; var folderSizeCurrent = properties["brs:sizeCurrent"] || ""; if(folderSizeCurrent != ""){ var k = 1024; // or 1024 for binary var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; var i = Math.floor(Math.log(folderSizeCurrent) / Math.log(k)); folderSizeCurrent = parseFloat((folderSizeCurrent / Math.pow(k, i)).toFixed()) + ' ' + sizes[i]; } html = '<span>' + label + folderSizeCurrent + '</span>'; return html; } });
We defined a new metadata template for the Folder Quota Aspect in our share extension module to display the Size Limit and Actual Size in the DocumentLibrary Details view. Also note that we have an evaluator to check for the Folder Quota Aspect and also using our custom renderer (seedFolderSizeCurrentCustomRendition) for displaying the current folder size property.
<module> <id>Add Folder quota Metadata Template</id> <version>1.0</version> <auto-deploy>true</auto-deploy> <configurations> <config evaluator="string-compare" condition="DocumentLibrary"> <metadata-templates> <template id="seedFolderQuotaMetadataTemplate"> <evaluator>seed.evaluator.doclib.action.hasQuotaAspect</evaluator> <banner index="10" id="lockBanner" evaluator="evaluator.doclib.metadata.hasLockBanner"> {lockBanner}</banner> <banner index="20" id="syncTransientError" evaluator="evaluator.doclib.metadata. hasSyncTransientErrorBanner">{syncTransientError}</banner> <banner index="30" id="syncFailed" evaluator="evaluator.doclib.metadata.hasSyncFailedBanner"> {syncFailed}</banner> <line index="10" id="date">{date}{size}</line> <line index="20" id="description" view="detailed">{description}</line> <line index="30" id="tags" view="detailed">{tags}</line> <line index="40" id="categories" view="detailed" evaluator="evaluator.doclib.metadata.hasCategories">{categories}</line> <line index="50" id="folderSize" view="detailed" evaluator="seed.evaluator.doclib.action.hasQuotaAspect"> {seed_sizeLimit seed.action.setFolderSizeLimit.form.field.sizeLimit} {seedFolderSizeCurrentCustomRendition seed.action.setFolderSizeLimit.form.field.sizeCurrent}</line> <line index="60" id="social" view="detailed">{social}</line> </template> </metadata-templates> </config> </configurations> </module>
This concludes our blog on how we can set up a specific limit on a folder in alfresco and hope it was helpful.