diff --git a/config/alfresco/application-context.xml b/config/alfresco/application-context.xml index 7b17e237bf..c39560b81e 100644 --- a/config/alfresco/application-context.xml +++ b/config/alfresco/application-context.xml @@ -24,7 +24,8 @@ - + + diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index 0dce034338..0e044ff6ef 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -201,9 +201,11 @@ d:long + true - d:long + d:long + true diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml index f1bec03ebf..f7698816a4 100644 --- a/config/alfresco/node-services-context.xml +++ b/config/alfresco/node-services-context.xml @@ -195,28 +195,6 @@ 5000 - - - - - - - - - - - - - - - - - - - - - ${system.usages.enabled} - diff --git a/config/alfresco/usage-services-context.xml b/config/alfresco/usage-services-context.xml new file mode 100644 index 0000000000..b6616fff38 --- /dev/null +++ b/config/alfresco/usage-services-context.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ${system.usages.enabled} + + + + workspace://SpacesStore + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java index b0d5f500c2..5df6be0c86 100644 --- a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java +++ b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java @@ -25,6 +25,7 @@ package org.alfresco.repo.usage; import java.io.Serializable; +import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; @@ -35,7 +36,6 @@ import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.usage.ContentQuotaException; import org.alfresco.service.cmr.usage.ContentUsageService; @@ -64,8 +64,7 @@ public class ContentUsageImpl implements ContentUsageService, private boolean enabled = true; - public static final StoreRef SPACES_STOREREF = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); - + private List stores; public void setNodeService(NodeService nodeService) { @@ -91,7 +90,16 @@ public class ContentUsageImpl implements ContentUsageService, { this.enabled = enabled; } + + public void setStores(List stores) + { + this.stores = stores; + } + public List getStores() + { + return this.stores; + } /** * The initialise method @@ -129,7 +137,7 @@ public class ContentUsageImpl implements ContentUsageService, public void onCreateNode(ChildAssociationRef childAssocRef) { NodeRef nodeRef = childAssocRef.getChildRef(); - if (nodeRef.getStoreRef().equals(SPACES_STOREREF)) + if (stores.contains(nodeRef.getStoreRef().toString())) { // Get content size @@ -165,7 +173,7 @@ public class ContentUsageImpl implements ContentUsageService, Map before, Map after) { - if (nodeRef.getStoreRef().equals(SPACES_STOREREF)) + if (stores.contains(nodeRef.getStoreRef().toString())) { // Check for change in content size @@ -258,7 +266,7 @@ public class ContentUsageImpl implements ContentUsageService, */ public void beforeDeleteNode(NodeRef nodeRef) { - if (nodeRef.getStoreRef().equals(SPACES_STOREREF)) + if (stores.contains(nodeRef.getStoreRef().toString())) { // TODO use data dictionary to get content property ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); diff --git a/source/java/org/alfresco/repo/usage/UsageQuotaProtector.java b/source/java/org/alfresco/repo/usage/UsageQuotaProtector.java new file mode 100644 index 0000000000..17a388ccd3 --- /dev/null +++ b/source/java/org/alfresco/repo/usage/UsageQuotaProtector.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.usage; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.usage.ContentUsageService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Implements policies/behaviour for protecting system/admin-maintained person properties + * + */ +public class UsageQuotaProtector implements NodeServicePolicies.OnUpdatePropertiesPolicy +{ + private AuthorityService authorityService; + private AuthenticationService authenticationService; + private PolicyComponent policyComponent; + private ContentUsageService contentUsageService; + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setContentUsageService(ContentUsageService contentUsageService) + { + this.contentUsageService = contentUsageService; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * The initialise method + */ + public void init() + { + if (contentUsageService.getEnabled()) + { + // Register interest in the onUpdateProperties policy + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), + ContentModel.TYPE_PERSON, + new JavaBehaviour(this, "onUpdateProperties")); + } + } + + /** + * Called after a node's properties have been changed. + * + * @param nodeRef reference to the updated node + * @param before the node's properties before the change + * @param after the node's properties after the change + */ + public void onUpdateProperties( + NodeRef nodeRef, + Map before, + Map after) + { + Long sizeCurrentBefore = (Long)before.get(ContentModel.PROP_SIZE_CURRENT); + Long sizeCurrentAfter = (Long)after.get(ContentModel.PROP_SIZE_CURRENT); + + Long sizeQuotaBefore = (Long)before.get(ContentModel.PROP_SIZE_QUOTA); + Long sizeQuotaAfter = (Long)after.get(ContentModel.PROP_SIZE_QUOTA); + + // Check for change in sizeCurrent + if ((sizeCurrentBefore != sizeCurrentAfter) && (! (authorityService.hasAdminAuthority() || authenticationService.isCurrentUserTheSystemUser()))) + { + throw new AlfrescoRuntimeException("Update failed: protected property 'sizeCurrent'"); + } + + // Check for change in sizeQuota + if ((sizeQuotaBefore != sizeQuotaAfter) && (! authorityService.hasAdminAuthority())) + { + throw new AlfrescoRuntimeException("Update failed: protected property 'sizeQuota'"); + } + } +} diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java index 696eaf256c..dcf60ec490 100644 --- a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java +++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java @@ -26,11 +26,13 @@ package org.alfresco.repo.usage; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.Node; import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.TransactionServiceImpl; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -201,52 +203,56 @@ public class UserUsageTrackingComponent else { // Collapse usage deltas (if a person has initial usage set) - RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); - // wrap to make the request in a transaction - RetryingTransactionCallback collapseUsages = new RetryingTransactionCallback() + // wrap to make the request in a transaction and run as System user + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - public Object execute() throws Throwable + public Object doWork() throws Exception { - // Get distinct candidates - Set usageNodeRefs = usageService.getUsageDeltaNodes(); - - for(NodeRef usageNodeRef : usageNodeRefs) - { - QName nodeType = nodeService.getType(usageNodeRef); - - if (nodeType.equals(ContentModel.TYPE_PERSON)) + return txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Throwable { - NodeRef personNodeRef = usageNodeRef; - String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + // Get distinct candidates + Set usageNodeRefs = usageService.getUsageDeltaNodes(); - long currentUsage = contentUsageImpl.getUserStoredUsage(personNodeRef); - if (currentUsage != -1) - { - // collapse the usage deltas - currentUsage = contentUsageImpl.getUserUsage(userName); - usageService.deleteDeltas(personNodeRef); - contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage); + for(NodeRef usageNodeRef : usageNodeRefs) + { + QName nodeType = nodeService.getType(usageNodeRef); - if (logger.isDebugEnabled()) + if (nodeType.equals(ContentModel.TYPE_PERSON)) { - logger.debug("Collapsed usage: username=" + userName + ", usage=" + currentUsage); + NodeRef personNodeRef = usageNodeRef; + String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); + + long currentUsage = contentUsageImpl.getUserStoredUsage(personNodeRef); + if (currentUsage != -1) + { + // collapse the usage deltas + currentUsage = contentUsageImpl.getUserUsage(userName); + usageService.deleteDeltas(personNodeRef); + contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage); + + if (logger.isDebugEnabled()) + { + logger.debug("Collapsed usage: username=" + userName + ", usage=" + currentUsage); + } + } + else + { + if (logger.isWarnEnabled()) + { + logger.warn("Initial usage for user has not yet been calculated: " + userName); + } + } } - } - else - { - if (logger.isWarnEnabled()) - { - logger.warn("Initial usage for user has not yet been calculated: " + userName); - } - } + } + return null; } - } - return null; + }); } - }; - - txnHelper.doInTransaction(collapseUsages, false); + }, AuthenticationUtil.getSystemUserName()); } } } @@ -265,45 +271,50 @@ public class UserUsageTrackingComponent */ public void recalculateUsage(final String userName) { - final StoreRef storeRef = ContentUsageImpl.SPACES_STOREREF; - - RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + final RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); // wrap to make the request in a transaction RetryingTransactionCallback calculatePersonCurrentUsage = new RetryingTransactionCallback() { public Long execute() throws Throwable { - // get nodes for which user is owner - Collection ownerNodes = nodeDaoService.getNodesWithPropertyStringValueForStore(storeRef, ContentModel.PROP_OWNER, userName); - + List stores = contentUsageImpl.getStores(); long totalUsage = 0; - for (Node ownerNode : ownerNodes) + + for (String store : stores) { - if (ownerNode.getTypeQName().equals(ContentModel.TYPE_CONTENT)) + StoreRef storeRef = new StoreRef(store); + + // get nodes for which user is owner + Collection ownerNodes = nodeDaoService.getNodesWithPropertyStringValueForStore(storeRef, ContentModel.PROP_OWNER, userName); + + for (Node ownerNode : ownerNodes) { - ContentData contentData = ContentData.createContentProperty(ownerNode.getProperties().get(ContentModel.PROP_CONTENT).getStringValue()); - totalUsage = totalUsage + contentData.getSize(); + if (ownerNode.getTypeQName().equals(ContentModel.TYPE_CONTENT)) + { + ContentData contentData = ContentData.createContentProperty(ownerNode.getProperties().get(ContentModel.PROP_CONTENT).getStringValue()); + totalUsage = totalUsage + contentData.getSize(); + } } - } - - // get nodes for which user is creator, and then filter out those that have an owner - Collection creatorNodes = nodeDaoService.getNodesWithPropertyStringValueForStore(storeRef, ContentModel.PROP_CREATOR, userName); - - for (Node creatorNode : creatorNodes) - { - if (creatorNode.getTypeQName().equals(ContentModel.TYPE_CONTENT) && - creatorNode.getProperties().get(ContentModel.PROP_OWNER) == null) + + // get nodes for which user is creator, and then filter out those that have an owner + Collection creatorNodes = nodeDaoService.getNodesWithPropertyStringValueForStore(storeRef, ContentModel.PROP_CREATOR, userName); + + for (Node creatorNode : creatorNodes) { - ContentData contentData = ContentData.createContentProperty(creatorNode.getProperties().get(ContentModel.PROP_CONTENT).getStringValue()); - totalUsage = totalUsage + contentData.getSize(); + if (creatorNode.getTypeQName().equals(ContentModel.TYPE_CONTENT) && + creatorNode.getProperties().get(ContentModel.PROP_OWNER) == null) + { + ContentData contentData = ContentData.createContentProperty(creatorNode.getProperties().get(ContentModel.PROP_CONTENT).getStringValue()); + totalUsage = totalUsage + contentData.getSize(); + } + } + + if (logger.isDebugEnabled()) + { + long quotaSize = contentUsageImpl.getUserQuota(userName); + logger.debug("Recalc usage ("+ userName+") totalUsage="+totalUsage+", quota="+quotaSize); } - } - - if (logger.isDebugEnabled()) - { - long quotaSize = contentUsageImpl.getUserQuota(userName); - logger.debug("Recalc usage ("+ userName+") totalUsage="+totalUsage+", quota="+quotaSize); } return totalUsage; @@ -312,17 +323,22 @@ public class UserUsageTrackingComponent // execute in READ-ONLY txn final Long currentUsage = txnHelper.doInTransaction(calculatePersonCurrentUsage, true); - // wrap to make the request in a transaction - RetryingTransactionCallback setUserCurrentUsage = new RetryingTransactionCallback() + // wrap to make the request in a transaction and run as System user + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - public Object execute() throws Throwable + public Object doWork() throws Exception { - NodeRef personNodeRef = personService.getPerson(userName); - contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage); - usageService.deleteDeltas(personNodeRef); - return null; + return txnHelper.doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + NodeRef personNodeRef = personService.getPerson(userName); + contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage); + usageService.deleteDeltas(personNodeRef); + return null; + } + }); } - }; - txnHelper.doInTransaction(setUserCurrentUsage, false); + }, AuthenticationUtil.getSystemUserName()); } }