diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index d454ce6684..0600983779 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -215,9 +215,6 @@ - - noop://noop - diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index de3cf5d4c9..7c4d303865 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -260,6 +260,9 @@ + + + diff --git a/source/java/org/alfresco/repo/avm/AVMInterpreter.java b/source/java/org/alfresco/repo/avm/AVMInterpreter.java index 2158b9e659..9f4491d722 100644 --- a/source/java/org/alfresco/repo/avm/AVMInterpreter.java +++ b/source/java/org/alfresco/repo/avm/AVMInterpreter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 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 diff --git a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java index c2688b049e..6e31b57b88 100644 --- a/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java +++ b/source/java/org/alfresco/repo/config/xml/RepoXMLConfigService.java @@ -202,21 +202,7 @@ public class RepoXMLConfigService extends XMLConfigService implements TenantDepl @Override protected void onShutdown(ApplicationEvent event) { - // run as System on shutdown - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - destroy(); - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - if ((tenantDeployerService != null) && (tenantDeployerService.isEnabled())) - { - tenantDeployerService.undeployTenants(this, logger); - tenantDeployerService.unregister(this); - } + // NOOP } public void onEnableTenant() diff --git a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java index 7012c2405f..1bed401b29 100644 --- a/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java +++ b/source/java/org/alfresco/repo/dictionary/DictionaryRepositoryBootstrap.java @@ -408,22 +408,7 @@ public class DictionaryRepositoryBootstrap extends AbstractLifecycleBean impleme @Override protected void onShutdown(ApplicationEvent event) { - unregister(); - - // run as System on shutdown - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() - { - destroy(); - return null; - } - }, AuthenticationUtil.getSystemUserName()); - - if (tenantDeployerService.isEnabled()) - { - tenantDeployerService.undeployTenants(this, logger); - } + // NOOP } public void onEnableTenant() diff --git a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java index ff04373f14..e1c7fdca67 100644 --- a/source/java/org/alfresco/repo/importer/ImporterBootstrap.java +++ b/source/java/org/alfresco/repo/importer/ImporterBootstrap.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2007 Alfresco Software Limited. + * Copyright (C) 2005-2008 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 @@ -294,7 +294,7 @@ public class ImporterBootstrap extends AbstractLifecycleBean } /** - * Boostrap the Repository + * Bootstrap the Repository */ public void bootstrap() { @@ -302,7 +302,15 @@ public class ImporterBootstrap extends AbstractLifecycleBean PropertyCheck.mandatory(this, "namespaceService", namespaceService); PropertyCheck.mandatory(this, "nodeService", nodeService); PropertyCheck.mandatory(this, "importerService", importerService); - PropertyCheck.mandatory(this, "storeRef", storeRef); + + if (storeRef == null) + { + if (logger.isDebugEnabled()) + { + logger.debug("No Store URL - bootstrap import ignored"); + } + return; + } // note: in MT case, this will run in System context of tenant domain Authentication authentication = authenticationComponent.getCurrentAuthentication(); diff --git a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java index 56e20f363b..6b6b0ce1e8 100644 --- a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java +++ b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java @@ -25,8 +25,10 @@ package org.alfresco.repo.usage; import java.io.Serializable; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.node.NodeServicePolicies; @@ -34,6 +36,7 @@ import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; @@ -59,6 +62,15 @@ public class ContentUsageImpl implements ContentUsageService, // Logger private static Log logger = LogFactory.getLog(ContentUsageImpl.class); + /** Key to the deleted nodes */ + private static final String KEY_DELETED_NODES = "contentUsage.deletedNodes"; + + /** Key to the updated nodes */ + private static final String KEY_UPDATED_NODES = "contentUsage.updatedNodes"; + + /** Key to the deleted folder */ + private static final String KEY_DELETED_FOLDER = "contentUsage.deletedFolder"; + private NodeService nodeService; private PersonService personService; private PolicyComponent policyComponent; @@ -122,26 +134,97 @@ public class ContentUsageImpl implements ContentUsageService, { if (enabled) { - // Register interest in the onCreateNode policy + // Register interest in the onCreateNode policy - for content policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), ContentModel.TYPE_CONTENT, new JavaBehaviour(this, "onCreateNode")); - // Register interest in the onUpdateProperties policy + // Register interest in the onUpdateProperties policy - for content policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), ContentModel.TYPE_CONTENT, new JavaBehaviour(this, "onUpdateProperties")); - // Register interest in the beforeDeleteNode policy + // Register interest in the beforeDeleteNode policy - for content policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), ContentModel.TYPE_CONTENT, new JavaBehaviour(this, "beforeDeleteNode")); + + // Register interest in the onUpdateNode policy - for content + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateNode"), + ContentModel.TYPE_CONTENT, + new JavaBehaviour(this, "onUpdateNode")); + + // Register interest in the beforeDeleteNode policy - for folder + policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), + ContentModel.TYPE_FOLDER, + new JavaBehaviour(this, "beforeDeleteNode")); } } + @SuppressWarnings("unchecked") + private void recordDelete(NodeRef nodeRef) + { + Set deletedNodes = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_NODES); + if (deletedNodes == null) + { + deletedNodes = new HashSet(); + AlfrescoTransactionSupport.bindResource(KEY_DELETED_NODES, deletedNodes); + } + deletedNodes.add(tenantService.getName(nodeRef)); + } + + @SuppressWarnings("unchecked") + private boolean alreadyDeleted(NodeRef nodeRef) + { + Set deletedNodes = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_NODES); + if (deletedNodes != null) + { + for (NodeRef deletedNodeRef : deletedNodes) + { + if (deletedNodeRef.equals(nodeRef)) + { + if (logger.isDebugEnabled()) logger.debug("alreadyDeleted: nodeRef="+nodeRef); + return true; + } + } + } + return false; + } + + @SuppressWarnings("unchecked") + private void recordUpdate(NodeRef nodeRef) + { + Set updatedNodes = (Set)AlfrescoTransactionSupport.getResource(KEY_UPDATED_NODES); + if (updatedNodes == null) + { + updatedNodes = new HashSet(); + AlfrescoTransactionSupport.bindResource(KEY_UPDATED_NODES, updatedNodes); + } + updatedNodes.add(tenantService.getName(nodeRef)); + } + + @SuppressWarnings("unchecked") + private boolean alreadyUpdated(NodeRef nodeRef) + { + Set updatedNodes = (Set)AlfrescoTransactionSupport.getResource(KEY_UPDATED_NODES); + if (updatedNodes != null) + { + for (NodeRef updatedNodeRef : updatedNodes) + { + if (updatedNodeRef.equals(nodeRef)) + { + if (logger.isDebugEnabled()) logger.debug("alreadyUpdated: nodeRef="+nodeRef); + return true; + } + } + } + return false; + } /** * Called when a new node has been created. @@ -151,7 +234,7 @@ public class ContentUsageImpl implements ContentUsageService, public void onCreateNode(ChildAssociationRef childAssocRef) { NodeRef nodeRef = childAssocRef.getChildRef(); - if (stores.contains(tenantService.getBaseName(nodeRef.getStoreRef()).toString())) + if (stores.contains(tenantService.getBaseName(nodeRef.getStoreRef()).toString()) && (! alreadyUpdated(nodeRef))) { // Get content size @@ -175,6 +258,39 @@ public class ContentUsageImpl implements ContentUsageService, } } + public void onUpdateNode(NodeRef nodeRef) + { + NodeRef folderNodeRef = (NodeRef)AlfrescoTransactionSupport.getResource(KEY_DELETED_FOLDER); + if (folderNodeRef != null) + { + // TODO partial fix for ETWONINE-43 + // there must be a better way, eg. refine move policies ... currently assumes updated node exists in deleted folder hierarchy and has been moved from workspace to the archive + if (nodeRef.toString().contains("archive://") && (! alreadyDeleted(nodeRef))) + { + ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (contentData != null) + { + long contentSize = contentData.getSize(); + + // Get owner/creator + String owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER); + if (owner == null) + { + owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_CREATOR); + } + + if (contentSize != 0 && owner != null) + { + // decrement usage if node is being deleted + if (logger.isDebugEnabled()) logger.debug("onUpdateNode: folderRef="+folderNodeRef+", archivedContentNodeRef="+nodeRef+", owner="+owner+", contentSize="+contentSize); + decrementUserUsage(owner, contentSize, nodeRef); + recordDelete(nodeRef); + } + } + } + } + } + /** * Called after a node's properties have been changed. * @@ -215,13 +331,14 @@ public class ContentUsageImpl implements ContentUsageService, // new size has been added - note: ownerBefore does not matter since the contentSizeBefore is null if (logger.isDebugEnabled()) logger.debug("onUpdateProperties: updateSize (null -> "+contentSizeAfter+"): nodeRef="+nodeRef+", ownerAfter="+ownerAfter); incrementUserUsage(ownerAfter, contentSizeAfter, nodeRef); - + recordUpdate(nodeRef); } else if (contentSizeAfter == null && contentSizeBefore != null && contentSizeBefore != 0 && ownerBefore != null) { // old size has been removed - note: ownerAfter does not matter since contentSizeAfter is null if (logger.isDebugEnabled()) logger.debug("onUpdateProperties: updateSize ("+contentSizeBefore+" -> null): nodeRef="+nodeRef+", ownerBefore="+ownerBefore); decrementUserUsage(ownerBefore, contentSizeBefore, nodeRef); + recordUpdate(nodeRef); } else if (contentSizeBefore != null && contentSizeAfter != null) { @@ -233,10 +350,12 @@ public class ContentUsageImpl implements ContentUsageService, if (contentSizeBefore != 0 && ownerBefore != null) { decrementUserUsage(ownerBefore, contentSizeBefore, nodeRef); + recordUpdate(nodeRef); } if (contentSizeAfter != 0 && ownerAfter != null) { incrementUserUsage(ownerAfter, contentSizeAfter, nodeRef); + recordUpdate(nodeRef); } } else @@ -247,12 +366,14 @@ public class ContentUsageImpl implements ContentUsageService, // new owner has been added if (logger.isDebugEnabled()) logger.debug("onUpdateProperties: updateOwner (null -> "+ownerAfter+"): nodeRef="+nodeRef+", contentSize="+contentSizeAfter); incrementUserUsage(ownerAfter, contentSizeAfter, nodeRef); + recordUpdate(nodeRef); } else if (ownerAfter == null && ownerBefore != null && contentSizeBefore != 0) { // old owner has been removed if (logger.isDebugEnabled()) logger.debug("onUpdateProperties: updateOwner ("+ownerBefore+" -> null): nodeRef="+nodeRef+", contentSize="+contentSizeBefore); decrementUserUsage(ownerBefore, contentSizeBefore, nodeRef); + recordUpdate(nodeRef); } else if (ownerBefore != null && ownerAfter != null && ownerBefore.equals(ownerAfter) == false) { @@ -262,10 +383,12 @@ public class ContentUsageImpl implements ContentUsageService, if (contentSizeBefore != 0) { decrementUserUsage(ownerBefore, contentSizeBefore, nodeRef); + recordUpdate(nodeRef); } if (contentSizeAfter != 0) { incrementUserUsage(ownerAfter, contentSizeAfter, nodeRef); + recordUpdate(nodeRef); } } } @@ -280,22 +403,38 @@ public class ContentUsageImpl implements ContentUsageService, */ public void beforeDeleteNode(NodeRef nodeRef) { - if (stores.contains(tenantService.getBaseName(nodeRef.getStoreRef()).toString())) + if (stores.contains(tenantService.getBaseName(nodeRef.getStoreRef()).toString()) && (! alreadyDeleted(nodeRef))) { - // TODO use data dictionary to get content property - ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - - if (contentData != null) + QName type = nodeService.getType(nodeRef); + if (type.equals(ContentModel.TYPE_CONTENT)) { - long contentSize = contentData.getSize(); - String owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER); + // TODO use data dictionary to get content property + ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); - if (contentSize != 0 && owner != null) - { - // decrement usage if node is being deleted - if (logger.isDebugEnabled()) logger.debug("beforeDeleteNode: nodeRef="+nodeRef+", owner="+owner+", contentSize="+contentSize); - decrementUserUsage(owner, contentSize, nodeRef); - } + if (contentData != null) + { + long contentSize = contentData.getSize(); + + // Get owner/creator + String owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER); + if (owner == null) + { + owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_CREATOR); + } + + if (contentSize != 0 && owner != null) + { + // decrement usage if node is being deleted + if (logger.isDebugEnabled()) logger.debug("beforeDeleteNode: nodeRef="+nodeRef+", owner="+owner+", contentSize="+contentSize); + decrementUserUsage(owner, contentSize, nodeRef); + recordDelete(nodeRef); + } + } + } + else if (type.equals(ContentModel.TYPE_FOLDER)) + { + if (logger.isDebugEnabled()) logger.debug("beforeDeleteNode: folderNodeRef="+nodeRef); + AlfrescoTransactionSupport.bindResource(KEY_DELETED_FOLDER, nodeRef); } } } diff --git a/source/java/org/alfresco/repo/usage/UserUsageTest.java b/source/java/org/alfresco/repo/usage/UserUsageTest.java new file mode 100644 index 0000000000..7ef7626db7 --- /dev/null +++ b/source/java/org/alfresco/repo/usage/UserUsageTest.java @@ -0,0 +1,523 @@ +/* + * Copyright (C) 2005-2008 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.HashMap; +import java.util.Map; + +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; +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.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.usage.UsageService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * User Usage unit test + */ +public class UserUsageTest extends TestCase +{ + private static ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); + + protected NodeService nodeService; + + protected FileFolderService fileFolderService; + + protected AuthenticationService authenticationService; + + private MutableAuthenticationDao authenticationDAO; + + protected NodeRef rootNodeRef; + + protected NodeRef systemNodeRef; + + protected NodeRef personNodeRef; + + protected AuthenticationComponent authenticationComponent; + + private UserTransaction testTX; + + private TransactionService transactionService; + + private ContentService contentService; + + private PersonService personService; + + private ContentUsageImpl contentUsageImpl; + + private UsageService usageService; + + private static final String TEST_USER = "userUsageTestUser"; + + protected void setUp() throws Exception + { + nodeService = (NodeService) applicationContext.getBean("nodeService"); + fileFolderService = (FileFolderService) applicationContext.getBean("fileFolderService"); + + authenticationService = (AuthenticationService) applicationContext.getBean("authenticationService"); + authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); + + authenticationComponent.setCurrentUser(authenticationComponent.getSystemUserName()); + authenticationDAO = (MutableAuthenticationDao) applicationContext.getBean("authenticationDao"); + transactionService = (TransactionService) applicationContext.getBean("transactionComponent"); + + contentService = (ContentService) applicationContext.getBean("contentService"); + personService = (PersonService) applicationContext.getBean("personService"); + + contentUsageImpl = (ContentUsageImpl) applicationContext.getBean("contentUsageImpl"); + usageService = (UsageService) applicationContext.getBean("usageService"); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + this.authenticationComponent.setSystemUserAsCurrentUser(); + + // get default store (as configured for content usage service) + StoreRef storeRef = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + rootNodeRef = nodeService.getRootNode(storeRef); + + // create person + if (personService.personExists(TEST_USER)) + { + personService.deletePerson(TEST_USER); + } + + Map props = createPersonProperties(TEST_USER); + personNodeRef = personService.createPerson(props); + + // create an authentication object e.g. the user + if (authenticationDAO.userExists(TEST_USER)) + { + authenticationService.deleteAuthentication(TEST_USER); + } + authenticationService.createAuthentication(TEST_USER, TEST_USER.toCharArray()); + + authenticationComponent.clearCurrentSecurityContext(); + } + + protected void tearDown() throws Exception + { + try + { + usageService.deleteDeltas(personNodeRef); + + testTX.commit(); + } + catch (Throwable t) + { + t.printStackTrace(); + } + finally + { + AuthenticationUtil.clearCurrentSecurityContext(); + super.tearDown(); + } + } + + protected void runAs(String userName) + { + authenticationService.authenticate(userName, userName.toCharArray()); + assertNotNull(authenticationService.getCurrentUserName()); + } + + private Map createPersonProperties(String userName) + { + HashMap properties = new HashMap(); + properties.put(ContentModel.PROP_USERNAME, userName); + return properties; + } + + public void testCreateUpdatedeleteInTx() throws Exception + { + runAs(TEST_USER); + + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + + // Create a folder + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "testFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // add content (in this case, some "panagrams") + + NodeRef content1 = addTextContent(folder, "text1.txt", "The quick brown fox jumps over the lazy dog"); // + 43 + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + NodeRef content2 = addTextContent(folder, "text2.txt", "Amazingly few discotheques provide jukeboxes"); // + 44 + assertEquals(87, contentUsageImpl.getUserUsage(TEST_USER)); + + NodeRef content3 = addTextContent(folder, "text3.txt", "All questions asked by five watch experts amazed the judge"); // + 58 + assertEquals(145, contentUsageImpl.getUserUsage(TEST_USER)); + + // update content in a different order + + updateTextContent(content1, "Few black taxis drive up major roads on quiet hazy nights"); // -43 + 57 = +14 + assertEquals(159, contentUsageImpl.getUserUsage(TEST_USER)); + + updateTextContent(content3, "Heavy boxes perform quick waltzes and jigs"); // -58 + 42 = -16 + assertEquals(143, contentUsageImpl.getUserUsage(TEST_USER)); + + updateTextContent(content2, "The five boxing wizards jump quickly"); // -44 + 36 = -8 + assertEquals(135, contentUsageImpl.getUserUsage(TEST_USER)); + + // delete content in a different order + + delete(content2); // - 36 + assertEquals(99, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content3); // - 42 + assertEquals(57, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content1); // - 57 + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + } + + public void testCreateUpdatedeleteAcrossTx() throws Exception + { + runAs(TEST_USER); + + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + + // Create a folder + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "testFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // add content (in this case, some "panagrams") + + NodeRef content1 = addTextContent(folder, "tqbfjotld.txt", "The quick brown fox jumps over the lazy dog"); + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + NodeRef content2 = addTextContent(folder, "afdpj.txt", "Amazingly few discotheques provide jukeboxes"); + assertEquals(87, contentUsageImpl.getUserUsage(TEST_USER)); + + NodeRef content3 = addTextContent(folder, "aqabfweatj.txt", "All questions asked by five watch experts amazed the judge"); + assertEquals(145, contentUsageImpl.getUserUsage(TEST_USER)); + + testTX.commit(); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + runAs(TEST_USER); + + // update content in a different order + + updateTextContent(content1, "Few black taxis drive up major roads on quiet hazy nights"); // -43 + 57 = +14 + assertEquals(159, contentUsageImpl.getUserUsage(TEST_USER)); + + updateTextContent(content3, "Heavy boxes perform quick waltzes and jigs"); // -58 + 42 = -16 + assertEquals(143, contentUsageImpl.getUserUsage(TEST_USER)); + + updateTextContent(content2, "The five boxing wizards jump quickly"); // -44 + 36 = -8 + assertEquals(135, contentUsageImpl.getUserUsage(TEST_USER)); + + testTX.commit(); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + runAs(TEST_USER); + + assertEquals(135, contentUsageImpl.getUserUsage(TEST_USER)); + + // delete content in a different order + + delete(content2); + assertEquals(99, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content3); + assertEquals(57, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content1); + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + } + + public void testCreateCopydeleteInTx() throws Exception + { + runAs(TEST_USER); + + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + + // Create a folder + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "testFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // add content + + // add panagram + NodeRef content1 = addTextContent(folder, "text1.txt", "The quick brown fox jumps over the lazy dog"); // + 43 + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + // copy content + + NodeRef content2 = copy(content1, folder, "Copy of text1.txt"); // + 43 + assertEquals(86, contentUsageImpl.getUserUsage(TEST_USER)); + + NodeRef content3 = copy(content1, folder, "Copy of Copy of text1.txt"); // + 43 + assertEquals(129, contentUsageImpl.getUserUsage(TEST_USER)); + + // delete content + + delete(content2); // - 43 + assertEquals(86, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content3); // - 43 + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content1); // - 43 + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + } + + public void testCreateCopydeleteAcrossTx() throws Exception + { + runAs(TEST_USER); + + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + + // Create a folder + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "testFolder"); + NodeRef folder = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // add content + + // add panagram + NodeRef content1 = addTextContent(folder, "text1.txt", "The quick brown fox jumps over the lazy dog"); // + 43 + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + testTX.commit(); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + runAs(TEST_USER); + + // copy content + + NodeRef content2 = copy(content1, folder, "Copy of text1.txt"); // + 43 + assertEquals(86, contentUsageImpl.getUserUsage(TEST_USER)); + + NodeRef content3 = copy(content1, folder, "Copy of Copy of text1.txt"); // + 43 + assertEquals(129, contentUsageImpl.getUserUsage(TEST_USER)); + + testTX.commit(); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + runAs(TEST_USER); + + assertEquals(129, contentUsageImpl.getUserUsage(TEST_USER)); + + // delete content + + delete(content2); // - 43 + assertEquals(86, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content3); // - 43 + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + delete(content1); // - 43 + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + } + + public void testCreateCopyDeleteFolderWithContentInTx() throws Exception + { + runAs(TEST_USER); + + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + + // Create a folder + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "testFolder"); + NodeRef folder1 = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // add content (in this case, some "panagrams") + + addTextContent(folder1, "text1.txt", "The quick brown fox jumps over the lazy dog"); // + 43 + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + addTextContent(folder1, "text2.txt", "Amazingly few discotheques provide jukeboxes"); // + 44 + assertEquals(87, contentUsageImpl.getUserUsage(TEST_USER)); + + addTextContent(folder1, "text3.txt", "All questions asked by five watch experts amazed the judge"); // + 58 + assertEquals(145, contentUsageImpl.getUserUsage(TEST_USER)); + + // copy folder with content + + NodeRef folder2 = copy(folder1, folder1, "Copy of testFolder"); // + 145 + assertEquals(290, contentUsageImpl.getUserUsage(TEST_USER)); + + // delete copied folder + + delete(folder2); // - 145 + assertEquals(145, contentUsageImpl.getUserUsage(TEST_USER)); + + // delete original folder + + delete(folder1); // - 145 + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + } + + public void testCreateCopyDeleteFolderWithContentAcrossTx() throws Exception + { + runAs(TEST_USER); + + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + + // Create a folder + Map folderProps = new HashMap(1); + folderProps.put(ContentModel.PROP_NAME, "testFolder"); + NodeRef folder1 = this.nodeService.createNode( + this.rootNodeRef, + ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), + ContentModel.TYPE_FOLDER).getChildRef(); + + // add content (in this case, some "panagrams") + + addTextContent(folder1, "text1.txt", "The quick brown fox jumps over the lazy dog"); // + 43 + assertEquals(43, contentUsageImpl.getUserUsage(TEST_USER)); + + addTextContent(folder1, "text2.txt", "Amazingly few discotheques provide jukeboxes"); // + 44 + assertEquals(87, contentUsageImpl.getUserUsage(TEST_USER)); + + addTextContent(folder1, "text3.txt", "All questions asked by five watch experts amazed the judge"); // + 58 + assertEquals(145, contentUsageImpl.getUserUsage(TEST_USER)); + + testTX.commit(); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + runAs(TEST_USER); + + // copy folder with content + + NodeRef folder2 = copy(folder1, folder1, "Copy of testFolder"); // + 145 + assertEquals(290, contentUsageImpl.getUserUsage(TEST_USER)); + + testTX.commit(); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + runAs(TEST_USER); + + // delete copied folder + + delete(folder2); // - 145 + assertEquals(145, contentUsageImpl.getUserUsage(TEST_USER)); + + testTX.commit(); + + testTX = transactionService.getUserTransaction(); + testTX.begin(); + runAs(TEST_USER); + + // delete original folder + + delete(folder1); // - 145 + assertEquals(0, contentUsageImpl.getUserUsage(TEST_USER)); + } + + private NodeRef addTextContent(NodeRef folderRef, String name, String textData) + { + Map contentProps = new HashMap(); + contentProps.put(ContentModel.PROP_NAME, name); + + ChildAssociationRef association = nodeService.createNode(folderRef, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, name), + ContentModel.TYPE_CONTENT, + contentProps); + + NodeRef content = association.getChildRef(); + + ContentWriter writer = contentService.getWriter(content, ContentModel.PROP_CONTENT, true); + + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + + writer.putContent(textData); + + return content; + } + + private void updateTextContent(NodeRef contentRef, String textData) + { + ContentWriter writer = contentService.getWriter(contentRef, ContentModel.PROP_CONTENT, true); + + writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + writer.setEncoding("UTF-8"); + + writer.putContent(textData); + } + + private void delete(NodeRef folderOrContentRef) + { + nodeService.deleteNode(folderOrContentRef); + } + + private NodeRef copy(NodeRef sourceFolderOrContentRef, NodeRef targetFolderRef, String newName) throws FileNotFoundException + { + return fileFolderService.copy(sourceFolderOrContentRef, targetFolderRef, newName).getNodeRef(); + } +} diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java index 2dd0645e87..7bb142a42b 100644 --- a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java +++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java @@ -31,6 +31,9 @@ import java.util.Set; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.Node; +import org.alfresco.repo.domain.PropertyValue; +import org.alfresco.repo.domain.QNameDAO; +import org.alfresco.repo.domain.QNameEntity; import org.alfresco.repo.node.db.NodeDaoService; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; @@ -62,6 +65,7 @@ public class UserUsageTrackingComponent private static boolean busy = false; private NodeDaoService nodeDaoService; + private QNameDAO qnameDAO; private TransactionServiceImpl transactionService; private ContentUsageImpl contentUsageImpl; @@ -78,6 +82,11 @@ public class UserUsageTrackingComponent this.nodeDaoService = nodeDaoService; } + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + public void setTransactionService(TransactionServiceImpl transactionService) { this.transactionService = transactionService; @@ -275,44 +284,73 @@ public class UserUsageTrackingComponent { public Long execute() throws Throwable { + QNameEntity ownerQnameEntity = qnameDAO.getQNameEntity(ContentModel.PROP_OWNER); + QNameEntity contentQnameEntity = qnameDAO.getQNameEntity(ContentModel.PROP_CONTENT); + List stores = contentUsageImpl.getStores(); long totalUsage = 0; - for (String store : stores) + if (contentQnameEntity != null) { - 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) + 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); + + if (logger.isDebugEnabled()) { - ContentData contentData = ContentData.createContentProperty(ownerNode.getProperties().get(ContentModel.PROP_CONTENT).getStringValue()); - totalUsage = totalUsage + contentData.getSize(); + logger.debug("Recalc usage ("+ userName+") store="+storeRef+", ownerNodeCount="+ownerNodes.size()); + } + + for (Node ownerNode : ownerNodes) + { + if (ownerNode.getTypeQName().equals(ContentModel.TYPE_CONTENT)) + { + PropertyValue content = ownerNode.getProperties().get(contentQnameEntity.getId()); + if (content != null) + { + ContentData contentData = ContentData.createContentProperty(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); + + if (logger.isDebugEnabled()) + { + logger.debug("Recalc usage ("+ userName+") store="+storeRef+", creatorNodeCount="+creatorNodes.size()); + } + + for (Node creatorNode : creatorNodes) + { + // note: it is possible for "owner" qname to be null (eg. ownership never taken) + if (creatorNode.getTypeQName().toString().equals(ContentModel.TYPE_CONTENT.toString()) && + ((ownerQnameEntity != null) && creatorNode.getProperties().get(ownerQnameEntity.getId()) == null)) + { + PropertyValue content = creatorNode.getProperties().get(contentQnameEntity.getId()); + if (content != null) + { + ContentData contentData = ContentData.createContentProperty(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) - { - 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); } } + else + { + logger.error("Failed to re-calculate usages - cannot find QName: " + ContentModel.PROP_CONTENT.toString()); + } return totalUsage; }