Alfresco: Default Quota Policy
Updated: Added new tested versions
For this post I want to share another policy from recent request from a customer. The project was to help them develop a way to have usage quotas set to a default value when a new user/person was added to Alfresco. (There is an important distinction between the two.) A few months ago I had a discussion about possible ways to implement this kind of functionality with a co-worker and had a few ideas brewing as we started the engagement.
First some background on quotas:
- The default quota in Alfresco is unlimited. In other words, there is no limit in the amount (total size) of content a user can add/own in Alfresco.
- A quota can be set either on creation or at anytime during a users lifetime.
- From within the UI a quota can be in either GB, MB or KB.
- From an API (Web Script, JavaScript, Java) it accepts the size in bytes.
- See http://wiki.alfresco.com/wiki/Usages_and_Quotas for additional details
When developing a behavior I continually reference Jeff Potts’s tutorial on implementing behaviors. His table of available policies is a great quick reference. Of course the definitive source is the Alfresco source code, but not much has changed since Jeff wrote the article.
JavaScript Behavior
The first attempt to implementing this policy was to use a behavior implemented in JavaScript. I used Jeff’s JavaScript example as the seed for my code. In fact there was very little I needed to change (mostly just the business logic). It is a great outline to get you going.
Using JavaScript wasn’t completely successful. The code did work up to a point and that point was setting the quota. There is an undocumented (in the wiki) JS function that can be used to set quotas.
From the People class (note the comment):
/** * Set the content quota in bytes for a person. * Only the admin authority can set this value. * * @param person Person to set quota against. * @param quota As a string, in bytes, a value of "-1" means no quota is set */ public void setQuota(ScriptNode person, String quota)
During the transaction the quota was being set, but once the transaction was committed it was lost. The problem being (again see the comments) that the setQuota must be executed by an admin. A quota must be set explicitly by an admin.
The JavaScript API lacks the ability to execute a script as one user but then run specific code in that script as another user. This is done for security reasons (like being able to keep a user from uploading and then executing a malicious script).
While the script did not work as desired it still may be useful for something else. So I’m including links to the context file and script as part of this post.
Java Behavior
Because we need to set the quota as an admin user, we’ll switch to Java which provides a useful means to run code as one user, and execute specific parts as a different user.
Before we jump to that, let’s talk about some of the specifics of our policy:
First, there is not much difference in the structure needed for this behavior and the Max Version Policy I wrote about in a previous post. So I used it as a template for this project.
Next, users/people in Alfresco are stored as nodes. If you start to dig into the node browser you may be drawn to find your users in the user://alfrecoUserStore store. This is, as the name suggestions, the Alfresco User Store which is used as the native Alfresco authentication source (alfrescoNTLM). The nodes in this store are of type {http://www.alfresco.org/model/user/1.0}user. Quotas are a property of {http://www.alfresco.org/model/content/1.0}person and are found in the workspace://SpacesStore under system -> people.
Alfresco has no specific policies for user nodes (like an onCreateUser policy), but since they are just nodes we can leverage the onCreateNode policy. Also because they are just nodes, when using an onCreateNode policy we don’t want the policy to be run every time a node is created. We want to target cm:person nodes. Otherwise, we would need to overcomplicate the code with additional tests to only only execute the business logic when the newly created node is of type cm:person. So we can bind our policy to a specific type. Similarly to the max version policy we initialize our behavior and bind it in the init method.
public void init() { this.onCreateNode = new JavaBehaviour(this, "onCreateNode", NotificationFrequency.TRANSACTION_COMMIT); this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.TYPE_PERSON, this.onCreateNode); }
In the init() above we bind our policy: QName.createQName(NamespaceService.ALFRESCO_URI, “onCreateNode”), the type: ContentModel.TYPE_PERSON and the behavior: this.onCreateNode together.
Next we need to implement the onCreateNode method from the OnCreateNodePolicy in our new class
public void onCreateNode(ChildAssociationRef childAssocRef) {
We can grab our reference to the newly created user node from the ChildAssociationRef parameter.
final NodeRef user = childAssocRef.getChildRef();
Notice that it is using the final modifier. Variables from an outer class that are being referenced in an inner class must be defined as final.
For our default policy we also want to allow the default quota to be overwritten. If an admin wants to set a different value for the quota at creation time we need to allow it. So we need to first get the value that was assigned to the user on the node at creation
long currentQuota = contentUsageService.getUserQuota((String) nodeService.getProperty(user,ContentModel.PROP_USERNAME));
The default value of no quota is -1. If an admin has set a value for the quota it will be greater than 0.
if (currentQuota < 0) { ...business logic here... }
Because quotas can only be set by an admin we need to use the runAs utility
AuthenticationUtil.runAs( new AuthenticationUtil.RunAsWork<Object>() { public Object doWork() throws Exception { ...code to be run as a different user... } }, "admin");
runAs takes two parameters: The first is the RunAsWork Object, which contains an inner class that has the code that needs to be run as another user. Second the name of the user to execute the code as.
Now in our policy we can set the quota. We’ll use the contentUsageService to set the new users quota, taking the username and the value of the quota as parameters. Also note that default value is in bytes which is read from the spring context file for the OTB default I’ve chosen 2GB (2147483648).
contentUsageService.setUserQuota((String) nodeService.getProperty(user, ContentModel.PROP_USERNAME), Long.parseLong(defaultQuota)); return user;
RunAsWork is also looking for a return value which I’ve set as user. We won’t being doing anything with this value so in this case it is an arbitrary return.
Installing
The policy is packaged as an AMP and is downloadable from the alfresco-defaultquota-policy project hosted on Google Code. (See http://wiki.alfresco.com/wiki/Module_Management_Tool for detailed instructions on installing AMPs.)
The amp has been tested with Alfresco Enterprise 3.1.1 to 3.3.2.
I’ve only tested this on Enterprise 3.3.2. I’ll look to expand my testing soon. If you have a chance to try it on another version of Alfresco please let me know. (In that case you’ll need to build the amp from source, changing the version.min/max property to include the version you’re targeting.)
So what next?
While this policy is limited to setting the user quota, your probably thinking what else can I use this for? I know I am! Maybe your using a database or some other none supported user store in Alfresco and you want to populate the user node properties with details from that system. Or you want to add them to specific groups based on some complex business logic. Or interact with external systems when a user is created in Alfresco. What are your ideas?
I also believe that this should work with any external authentication/synchronization of users into Alfresco. I’ve not tested it, but it is the list of things to test next.
I’m always looking for feedback: let me know what worked or didn’t for you.