diff --git a/config/alfresco/hibernate-context.xml b/config/alfresco/hibernate-context.xml
index 8b36c30b19..920ec7815d 100644
--- a/config/alfresco/hibernate-context.xml
+++ b/config/alfresco/hibernate-context.xml
@@ -46,7 +46,8 @@
org/alfresco/repo/domain/hibernate/AppliedPatch.hbm.xmlorg/alfresco/repo/domain/hibernate/Permission.hbm.xmlorg/alfresco/repo/avm/hibernate/AVM.hbm.xml
- org/alfresco/repo/attributes/hibernate/Attributes.hbm.xml
+ org/alfresco/repo/attributes/hibernate/Attributes.hbm.xml
+ org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml
@@ -228,7 +229,13 @@
-
+
+
+
+
+
+
+
@@ -238,6 +245,9 @@
+
+
+
diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml
index b394441108..c322d5e38e 100644
--- a/config/alfresco/model/contentModel.xml
+++ b/config/alfresco/model/contentModel.xml
@@ -184,7 +184,15 @@
d:text
-
+
+
+
+ d:long
+
+
+ d:long
+
+
diff --git a/config/alfresco/node-services-context.xml b/config/alfresco/node-services-context.xml
index a67086c5a8..f1bec03ebf 100644
--- a/config/alfresco/node-services-context.xml
+++ b/config/alfresco/node-services-context.xml
@@ -196,5 +196,27 @@
5000
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${system.usages.enabled}
+
+
+
diff --git a/config/alfresco/public-services-context.xml b/config/alfresco/public-services-context.xml
index 0ca29ab718..629d9afc17 100644
--- a/config/alfresco/public-services-context.xml
+++ b/config/alfresco/public-services-context.xml
@@ -563,6 +563,36 @@
${server.transaction.mode.default}
+
+
+
+
+
+
+ org.alfresco.service.cmr.usage.ContentUsageService
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${server.transaction.mode.default}
+
+
diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml
index dba1cbbc19..9739681b41 100644
--- a/config/alfresco/public-services-security-context.xml
+++ b/config/alfresco/public-services-security-context.xml
@@ -804,6 +804,13 @@
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index a29a913819..8fdebc7459 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -172,3 +172,6 @@ user.name.caseSensitive=false
# AVM Specific properties.
avm.remote.idlestream.timeout=30000
+
+# ECM content usages/quotas
+system.usages.enabled=true
diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml
index 494fd9f17b..17e6d5bf5b 100644
--- a/config/alfresco/scheduled-jobs-context.xml
+++ b/config/alfresco/scheduled-jobs-context.xml
@@ -231,4 +231,123 @@
0 30 3 * * ?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${system.usages.enabled}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${system.usages.enabled}
+
+
+ true
+
+
+
+
+
+
+
+
+ org.alfresco.repo.usage.UserUsageCollapseJob
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+ 5
+
+
+
+
+
+
+
+
+
+ org.alfresco.repo.usage.UserUsageBootstrapJob
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+ 0
+
+
+
+
\ No newline at end of file
diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties
index 61c9dc2dc7..0b9abb63c9 100644
--- a/config/alfresco/version.properties
+++ b/config/alfresco/version.properties
@@ -19,4 +19,4 @@ version.build=@build-number@
# Schema number
-version.schema=113
+version.schema=114
diff --git a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java
index a8668a640a..83ab646f82 100644
--- a/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java
+++ b/source/java/org/alfresco/filesys/smb/server/NTProtocolHandler.java
@@ -1482,8 +1482,15 @@ public class NTProtocolHandler extends CoreProtocolHandler
m_sess.sendErrorResponseSMB(SMBStatus.DOSInvalidData, SMBStatus.ErrDos);
return;
}
+ catch (DiskFullException ex)
+ {
+ m_sess.sendErrorResponseSMB(SMBStatus.NTDiskFull, SMBStatus.HRDWriteFault, SMBStatus.ErrHrd);
+ return;
+ }
catch (java.io.IOException ex)
{
+ if (logger.isDebugEnabled() && m_sess.hasDebug(SMBSrvSession.DBG_FILE))
+ logger.debug("IOException ignore: "+ex);
}
// Remove the file from the connections list of open files
diff --git a/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java b/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java
index 3bdf0778fc..3b9f883cd1 100644
--- a/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java
+++ b/source/java/org/alfresco/filesys/smb/server/repo/ContentNetworkFile.java
@@ -35,6 +35,7 @@ import java.nio.charset.Charset;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.server.filesys.AccessDeniedException;
+import org.alfresco.filesys.server.filesys.DiskFullException;
import org.alfresco.filesys.server.filesys.FileAttribute;
import org.alfresco.filesys.server.filesys.FileInfo;
import org.alfresco.filesys.server.filesys.FileOpenParams;
@@ -52,6 +53,7 @@ import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.usage.ContentQuotaException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -382,7 +384,14 @@ public class ContentNetworkFile extends NodeRefNetworkFile
// Update node properties
ContentData contentData = content.getContentData();
- nodeService.setProperty( getNodeRef(), ContentModel.PROP_CONTENT, contentData);
+ try
+ {
+ nodeService.setProperty( getNodeRef(), ContentModel.PROP_CONTENT, contentData);
+ }
+ catch (ContentQuotaException qe)
+ {
+ throw new DiskFullException(qe.getMessage());
+ }
}
else
{
diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java
index 30a6b82d0c..ad88e13643 100644
--- a/source/java/org/alfresco/model/ContentModel.java
+++ b/source/java/org/alfresco/model/ContentModel.java
@@ -168,6 +168,9 @@ public interface ContentModel
static final QName PROP_PRESENCEPROVIDER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "presenceProvider");
static final QName PROP_PRESENCEUSERNAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "presenceUsername");
+ static final QName PROP_SIZE_CURRENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "sizeCurrent");
+ static final QName PROP_SIZE_QUOTA = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "sizeQuota");
+
// Ownable aspect
static final QName ASPECT_OWNABLE = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "ownable");
static final QName PROP_OWNER = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "owner");
diff --git a/source/java/org/alfresco/repo/content/RoutingContentService.java b/source/java/org/alfresco/repo/content/RoutingContentService.java
index e6ee3e0c27..a57cd08d2e 100644
--- a/source/java/org/alfresco/repo/content/RoutingContentService.java
+++ b/source/java/org/alfresco/repo/content/RoutingContentService.java
@@ -59,6 +59,7 @@ import org.alfresco.service.cmr.repository.NoTransformerException;
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.usage.ContentQuotaException;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
@@ -567,6 +568,10 @@ public class RoutingContentService implements ContentService
" value: " + contentData);
}
}
+ catch (ContentQuotaException qe)
+ {
+ throw qe;
+ }
catch (Throwable e)
{
throw new ContentIOException("Failed to set content property on stream closure: \n" +
diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
index c707a0987a..a67c6e0cb5 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
+++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml
@@ -500,6 +500,19 @@
where
node.store.key.protocol = :protocol and
node.store.key.identifier = :identifier
-
+
+
+
+ select
+ node
+ from
+ org.alfresco.repo.domain.hibernate.NodeImpl as node
+ join node.properties prop
+ where
+ index(prop) = :propQName and
+ prop.stringValue = :propStringValue and
+ node.store.key.protocol = :protocol and
+ node.store.key.identifier = :identifier
+
diff --git a/source/java/org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml
new file mode 100644
index 0000000000..29247d9e71
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/hibernate/UsageDelta.hbm.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select
+ sum(deltaSize)
+ from
+ org.alfresco.repo.usage.hibernate.UsageDeltaImpl as usage_delta
+ where
+ usage_delta.node = :node
+
+
+
+
+
+ select
+ distinct usage_delta.node
+ from
+ org.alfresco.repo.usage.hibernate.UsageDeltaImpl as usage_delta
+
+
+
+
+ select
+ usage_delta
+ from
+ org.alfresco.repo.usage.hibernate.UsageDeltaImpl as usage_delta
+ where
+ usage_delta.node = :node
+
+
+
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java
index fa076e896c..ad15290081 100644
--- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java
+++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java
@@ -293,6 +293,8 @@ public interface NodeDaoService
*/
public int getNodeCount(final StoreRef storeRef);
+ public Collection getNodesWithPropertyStringValueForStore(final StoreRef storeRef, final QName propQName, final String propStringValue);
+
public Transaction getTxnById(long txnId);
/**
* Get all transactions in a given time range. Since time-based retrieval doesn't guarantee uniqueness
diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
index 55a5d97c04..a675442cd9 100644
--- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java
@@ -60,6 +60,7 @@ import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionAwareSingleton;
import org.alfresco.repo.transaction.TransactionalDao;
+import org.alfresco.repo.usage.UsageDeltaDAO;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
import org.alfresco.service.cmr.repository.AssociationExistsException;
@@ -113,6 +114,8 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
private static final String QUERY_GET_CHILD_ASSOCS_FOR_STORE = "node.GetChildAssocsForStore";
private static final String QUERY_GET_NODES_EXCEPT_ROOT_FOR_STORE = "node.GetNodesExceptRootForStore";
+ private static final String QUERY_NODES_WITH_PROPERTY_STRING_VALUE_FOR_STORE = "node.GetNodesWithPropertyStringValueForStore";
+
private static Log logger = LogFactory.getLog(HibernateNodeDaoServiceImpl.class);
/** Log to trace parent association caching: classname + .ParentAssocsCache */
private static Log loggerParentAssocsCache = LogFactory.getLog(HibernateNodeDaoServiceImpl.class.getName() + ".ParentAssocsCache");
@@ -131,7 +134,14 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
/** used for debugging */
private Set changeTxnIdSet;
- TenantService tenantService;
+ private UsageDeltaDAO usageDeltaDao;
+ private TenantService tenantService;
+
+
+ public void setUsageDeltaDao(UsageDeltaDAO usageDeltaDao)
+ {
+ this.usageDeltaDao = usageDeltaDao;
+ }
public void setTenantService(TenantService tenantService)
{
@@ -616,6 +626,13 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
{
getHibernateTemplate().delete(assoc);
}
+
+ if (isDebugEnabled)
+ {
+ logger.debug("Deleting usage deltas of node (if any)" + node.getId());
+ }
+ usageDeltaDao.deleteDeltas(node);
+
// update the node status
NodeRef nodeRef = node.getNodeRef();
NodeStatus nodeStatus = getNodeStatus(nodeRef, true);
@@ -1427,6 +1444,29 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements
// done
return count.intValue();
}
+
+
+ @SuppressWarnings("unchecked")
+ public Collection getNodesWithPropertyStringValueForStore(final StoreRef storeRef, final QName propQName, final String propStringValue)
+ {
+ HibernateCallback callback = new HibernateCallback()
+ {
+ public Object doInHibernate(Session session)
+ {
+ Query query = session.getNamedQuery(QUERY_NODES_WITH_PROPERTY_STRING_VALUE_FOR_STORE);
+ query.setString("protocol", storeRef.getProtocol())
+ .setString("identifier", storeRef.getIdentifier())
+ .setParameter("propQName", propQName)
+ .setString("propStringValue", propStringValue)
+ .setReadOnly(true);
+ return query.list();
+ }
+ };
+
+ List queryResults = (List) getHibernateTemplate().execute(callback);
+ return queryResults;
+ }
+
/*
* Queries for transactions
diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
index 0064f46e16..352ac35b45 100644
--- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
+++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java
@@ -607,6 +607,10 @@ public class PersonServiceImpl implements PersonService,
properties.put(ContentModel.PROP_EMAIL, "");
properties.put(ContentModel.PROP_ORGID, "");
properties.put(ContentModel.PROP_HOME_FOLDER_PROVIDER, defaultHomeFolderProvider);
+
+ properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
+ properties.put(ContentModel.PROP_SIZE_QUOTA, -1L); // no quota
+
return properties;
}
@@ -615,6 +619,9 @@ public class PersonServiceImpl implements PersonService,
String userName = DefaultTypeConverter.INSTANCE.convert(String.class, properties
.get(ContentModel.PROP_USERNAME));
properties.put(ContentModel.PROP_USERNAME, userName);
+
+ properties.put(ContentModel.PROP_SIZE_CURRENT, 0L);
+
return nodeService.createNode(getPeopleContainer(), ContentModel.ASSOC_CHILDREN, ContentModel.TYPE_PERSON,
ContentModel.TYPE_PERSON, properties).getChildRef();
}
diff --git a/source/java/org/alfresco/repo/usage/ContentUsageImpl.java b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java
new file mode 100644
index 0000000000..4f42251314
--- /dev/null
+++ b/source/java/org/alfresco/repo/usage/ContentUsageImpl.java
@@ -0,0 +1,412 @@
+/*
+ * 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.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.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.ContentUsageService;
+import org.alfresco.service.cmr.usage.UsageService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implements Content Usage service and policies/behaviour.
+ *
+ */
+public class ContentUsageImpl implements ContentUsageService,
+ NodeServicePolicies.OnCreateNodePolicy,
+ NodeServicePolicies.OnUpdatePropertiesPolicy,
+ NodeServicePolicies.BeforeDeleteNodePolicy
+{
+ // Logger
+ private static Log logger = LogFactory.getLog(ContentUsageImpl.class);
+
+ private NodeService nodeService;
+ private PersonService personService;
+ private PolicyComponent policyComponent;
+ private UsageService usageService;
+
+ private boolean enabled = true;
+
+ public static final StoreRef SPACES_STOREREF = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore");
+
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setPersonService(PersonService personService)
+ {
+ this.personService = personService;
+ }
+
+ public void setUsageService(UsageService usageService)
+ {
+ this.usageService = usageService;
+ }
+
+ public void setPolicyComponent(PolicyComponent policyComponent)
+ {
+ this.policyComponent = policyComponent;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+
+ /**
+ * The initialise method
+ */
+ public void init()
+ {
+ if (enabled)
+ {
+ // Register interest in the onCreateNode policy
+ policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"),
+ ContentModel.TYPE_CONTENT,
+ new JavaBehaviour(this, "onCreateNode"));
+
+ // Register interest in the onUpdateProperties policy
+ policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"),
+ ContentModel.TYPE_CONTENT,
+ new JavaBehaviour(this, "onUpdateProperties"));
+
+ // Register interest in the beforeDeleteNode policy
+ policyComponent.bindClassBehaviour(
+ QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"),
+ ContentModel.TYPE_CONTENT,
+ new JavaBehaviour(this, "beforeDeleteNode"));
+ }
+ }
+
+
+ /**
+ * Called when a new node has been created.
+ *
+ * @param childAssocRef the created child association reference
+ */
+ public void onCreateNode(ChildAssociationRef childAssocRef)
+ {
+ NodeRef nodeRef = childAssocRef.getChildRef();
+ if (nodeRef.getStoreRef().equals(SPACES_STOREREF))
+ {
+ // Get content size
+
+ // TODO use data dictionary to get content property
+ ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
+ Long contentSize = (contentData == null ? null : 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 != null && contentSize != 0 && owner != null)
+ {
+ // new node with non-empty content size
+ if (logger.isDebugEnabled()) logger.debug("onCreateNode: contentSize="+contentSize+", nodeRef="+nodeRef+", ownerAfter="+owner);
+ incrementUserUsage(owner, contentSize, nodeRef);
+ }
+ }
+ }
+
+ /**
+ * 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)
+ {
+ if (nodeRef.getStoreRef().equals(SPACES_STOREREF))
+ {
+ // Check for change in content size
+
+ // TODO use data dictionary to get content property
+ ContentData contentDataBefore = (ContentData)before.get(ContentModel.PROP_CONTENT);
+ Long contentSizeBefore = (contentDataBefore == null ? null : contentDataBefore.getSize());
+ ContentData contentDataAfter = (ContentData)after.get(ContentModel.PROP_CONTENT);
+ Long contentSizeAfter = (contentDataAfter == null ? null : contentDataAfter.getSize());
+
+ // Check for change in owner/creator
+ String ownerBefore = (String)before.get(ContentModel.PROP_OWNER);
+ if (ownerBefore == null)
+ {
+ ownerBefore = (String)before.get(ContentModel.PROP_CREATOR);
+ }
+ String ownerAfter = (String)after.get(ContentModel.PROP_OWNER);
+ if (ownerAfter == null)
+ {
+ ownerAfter = (String)after.get(ContentModel.PROP_CREATOR);
+ }
+
+ // check change in size (and possibly owner)
+ if (contentSizeBefore == null && contentSizeAfter != null && contentSizeAfter != 0 && ownerAfter != null)
+ {
+ // 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);
+
+ }
+ 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);
+ }
+ else if (contentSizeBefore != null && contentSizeAfter != null)
+ {
+ if (contentSizeBefore.equals(contentSizeAfter) == false)
+ {
+ // size has changed (and possibly owner)
+ if (logger.isDebugEnabled()) logger.debug("onUpdateProperties: updateSize ("+contentSizeBefore+" -> "+contentSizeAfter+"): nodeRef="+nodeRef+", ownerBefore="+ownerBefore+", ownerAfter="+ownerAfter);
+
+ if (contentSizeBefore != 0 && ownerBefore != null)
+ {
+ decrementUserUsage(ownerBefore, contentSizeBefore, nodeRef);
+ }
+ if (contentSizeAfter != 0 && ownerAfter != null)
+ {
+ incrementUserUsage(ownerAfter, contentSizeAfter, nodeRef);
+ }
+ }
+ else
+ {
+ // same size - check change in owner only
+ if (ownerBefore == null && ownerAfter != null && contentSizeAfter != 0 && ownerAfter != null)
+ {
+ // new owner has been added
+ if (logger.isDebugEnabled()) logger.debug("onUpdateProperties: updateOwner (null -> "+ownerAfter+"): nodeRef="+nodeRef+", contentSize="+contentSizeAfter);
+ incrementUserUsage(ownerAfter, contentSizeAfter, 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);
+ }
+ else if (ownerBefore != null && ownerAfter != null && ownerBefore.equals(ownerAfter) == false)
+ {
+ // owner has changed (size has not)
+ if (logger.isDebugEnabled()) logger.debug("onUpdateProperties: updateOwner ("+ownerBefore+" -> "+ownerAfter+"): nodeRef="+nodeRef+", contentSize="+contentSizeBefore);
+
+ if (contentSizeBefore != 0)
+ {
+ decrementUserUsage(ownerBefore, contentSizeBefore, nodeRef);
+ }
+ if (contentSizeAfter != 0)
+ {
+ incrementUserUsage(ownerAfter, contentSizeAfter, nodeRef);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Called before a node is deleted.
+ *
+ * @param nodeRef the node reference
+ */
+ public void beforeDeleteNode(NodeRef nodeRef)
+ {
+ if (nodeRef.getStoreRef().equals(SPACES_STOREREF))
+ {
+ // TODO use data dictionary to get content property
+ ContentData contentData = (ContentData)nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT);
+
+ if (contentData != null)
+ {
+ long contentSize = contentData.getSize();
+ String owner = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_OWNER);
+
+ 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);
+ }
+ }
+ }
+ }
+
+ private void incrementUserUsage(String userName, long contentSize, NodeRef contentNodeRef)
+ {
+ // increment usage - add positive delta
+ if (logger.isDebugEnabled()) logger.debug("incrementUserUsage: username="+userName+", contentSize="+contentSize+", contentNodeRef="+contentNodeRef);
+
+ long currentSize = getUserUsage(userName);
+ long quotaSize = getUserQuota(userName);
+
+ long newSize = currentSize + contentSize;
+
+ // check whether user's quota exceeded
+ if ((quotaSize != -1) && (newSize > quotaSize))
+ {
+ if (logger.isWarnEnabled())
+ {
+ logger.warn("User (" + userName + ") quota exceeded: content=" + contentSize +
+ ", usage=" + currentSize +
+ ", quota=" + quotaSize);
+ }
+ }
+
+ NodeRef personNodeRef = personService.getPerson(userName);
+ usageService.insertDelta(personNodeRef, contentSize);
+ }
+
+ private void decrementUserUsage(String userName, long contentSize, NodeRef contentNodeRef)
+ {
+ // decrement usage - add negative delta
+ if (logger.isDebugEnabled()) logger.debug("decrementUserUsage: username="+userName+", contentSize="+contentSize+", contentNodeRef="+contentNodeRef);
+
+ long currentSize = getUserUsage(userName);
+
+ long newSize = currentSize + contentSize;
+
+ if (newSize < 0)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("User (" + userName + ") has negative usage (" + newSize + ") - reset to 0");
+ }
+ }
+
+ NodeRef personNodeRef = personService.getPerson(userName);
+ usageService.insertDelta(personNodeRef, (-contentSize));
+ }
+
+ /**
+ * Set user's usage. Should only be called by background (collapse) job !
+ *
+ * @param userName
+ * @param currentUsage
+ */
+ public void setUserStoredUsage(NodeRef personNodeRef, long currentUsage)
+ {
+ if (personNodeRef != null)
+ {
+ nodeService.setProperty(personNodeRef, ContentModel.PROP_SIZE_CURRENT, new Long(currentUsage));
+ }
+ }
+
+ public long getUserStoredUsage(NodeRef personNodeRef)
+ {
+ Long currentUsage = null;
+ if (personNodeRef != null)
+ {
+ currentUsage = (Long)nodeService.getProperty(personNodeRef, ContentModel.PROP_SIZE_CURRENT);
+ }
+
+ return (currentUsage == null ? -1 : currentUsage);
+ }
+
+ public long getUserUsage(String userName)
+ {
+ long currentUsage = -1;
+
+ NodeRef personNodeRef = personService.getPerson(userName);
+ if (personNodeRef != null)
+ {
+ currentUsage = getUserStoredUsage(personNodeRef);
+ }
+
+ if (currentUsage != -1)
+ {
+ // add any deltas
+ currentUsage = currentUsage + usageService.getTotalDeltaSize(personNodeRef);
+
+ if (currentUsage < 0)
+ {
+ if (logger.isWarnEnabled())
+ {
+ logger.warn("User usage ("+ userName+") is negative ("+currentUsage+") overriding to 0");
+ }
+ currentUsage = 0;
+ }
+ }
+
+ return currentUsage;
+ }
+
+ /**
+ * Set user's current quota.
+ * Usually called by Web Client (Admin Console) if admin is changing/setting a user's quota.
+ *
+ * @param userName
+ * @param currentQuota
+ */
+ public void setUserQuota(String userName, long currentQuota)
+ {
+ NodeRef personNodeRef = personService.getPerson(userName);
+ if (personNodeRef != null)
+ {
+ nodeService.setProperty(personNodeRef, ContentModel.PROP_SIZE_QUOTA, new Long(currentQuota));
+ }
+ }
+
+ public long getUserQuota(String userName)
+ {
+ Long currentQuota = null;
+
+ NodeRef personNodeRef = personService.getPerson(userName);
+ if (personNodeRef != null)
+ {
+ currentQuota = (Long)nodeService.getProperty(personNodeRef, ContentModel.PROP_SIZE_QUOTA);
+ }
+
+ return (currentQuota == null ? -1 : currentQuota);
+ }
+
+ public boolean getEnabled()
+ {
+ return enabled;
+ }
+}
diff --git a/source/java/org/alfresco/repo/usage/UsageDelta.java b/source/java/org/alfresco/repo/usage/UsageDelta.java
new file mode 100644
index 0000000000..4f70145266
--- /dev/null
+++ b/source/java/org/alfresco/repo/usage/UsageDelta.java
@@ -0,0 +1,42 @@
+/*
+ * 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 org.alfresco.repo.domain.Node;
+
+/**
+ * Interface for persistent usage delta objects.
+ *
+ */
+public interface UsageDelta
+{
+ public Node getNode();
+
+ public void setNode(Node node);
+
+ public long getDeltaSize();
+
+ public void setDeltaSize(long usageDeltaSize);
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/usage/UsageDeltaDAO.java b/source/java/org/alfresco/repo/usage/UsageDeltaDAO.java
new file mode 100644
index 0000000000..8dd4e73857
--- /dev/null
+++ b/source/java/org/alfresco/repo/usage/UsageDeltaDAO.java
@@ -0,0 +1,56 @@
+/*
+ * 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.util.Set;
+
+import org.alfresco.repo.domain.Node;
+
+/**
+ * The interface to persist usage delta information.
+ *
+ */
+public interface UsageDeltaDAO
+{
+ /**
+ * Create a usage delta entry.
+ *
+ * @param deltaInfo
+ */
+ public void insertDelta(UsageDelta deltaInfo);
+
+ /**
+ * Get the total delta size for a node.
+ *
+ * @param node
+ * @return sum of delta sizes (in bytes) - can be +ve or -ve
+ */
+ public long getTotalDeltaSize(Node node);
+
+
+ public Set getUsageDeltaNodes();
+
+ public int deleteDeltas(Node node);
+}
diff --git a/source/java/org/alfresco/repo/usage/UsageServiceImpl.java b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java
new file mode 100644
index 0000000000..9eee352bee
--- /dev/null
+++ b/source/java/org/alfresco/repo/usage/UsageServiceImpl.java
@@ -0,0 +1,113 @@
+/*
+ * 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.util.HashSet;
+import java.util.Set;
+
+import org.alfresco.repo.domain.Node;
+import org.alfresco.repo.node.db.NodeDaoService;
+import org.alfresco.repo.tenant.TenantService;
+import org.alfresco.repo.usage.hibernate.UsageDeltaImpl;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.usage.UsageService;
+import org.alfresco.util.ParameterCheck;
+
+/**
+ * The implementation of the UsageService for tracking usages.
+ *
+ */
+public class UsageServiceImpl implements UsageService
+{
+ private UsageDeltaDAO usageDeltaDao;
+ private NodeDaoService nodeDaoService;
+ private TenantService tenantService;
+
+ //private static Log logger = LogFactory.getLog(UsageServiceImpl.class);
+
+
+ public void setUsageDeltaDao(UsageDeltaDAO usageDeltaDao)
+ {
+ this.usageDeltaDao = usageDeltaDao;
+ }
+
+ public void setNodeDaoService(NodeDaoService nodeDaoService)
+ {
+ this.nodeDaoService = nodeDaoService;
+ }
+
+ public void setTenantService(TenantService tenantService)
+ {
+ this.tenantService = tenantService;
+ }
+
+
+ public void insertDelta(NodeRef usageNodeRef, long deltaSize)
+ {
+ UsageDelta delta = new UsageDeltaImpl();
+
+ // delta properties
+ delta.setNode(getNodeNotNull(usageNodeRef));
+ delta.setDeltaSize(deltaSize);
+
+ usageDeltaDao.insertDelta(delta);
+ }
+
+ public long getTotalDeltaSize(NodeRef usageNodeRef)
+ {
+ return usageDeltaDao.getTotalDeltaSize(getNodeNotNull(usageNodeRef));
+ }
+
+ public Set getUsageDeltaNodes()
+ {
+ Set nodes = usageDeltaDao.getUsageDeltaNodes();
+
+ // convert nodes to nodeRefs
+ Set results = new HashSet(nodes.size());
+ for (Node node : nodes)
+ {
+ results.add(tenantService.getBaseName(node.getNodeRef()));
+ }
+ return results;
+ }
+
+ public int deleteDeltas(NodeRef usageNodeRef)
+ {
+ return usageDeltaDao.deleteDeltas(getNodeNotNull(usageNodeRef));
+ }
+
+ private Node getNodeNotNull(NodeRef nodeRef) throws InvalidNodeRefException
+ {
+ ParameterCheck.mandatory("nodeRef", nodeRef);
+
+ Node unchecked = nodeDaoService.getNode(tenantService.getName(nodeRef));
+ if (unchecked == null)
+ {
+ throw new InvalidNodeRefException("Node does not exist: " + nodeRef, nodeRef);
+ }
+ return unchecked;
+ }
+}
diff --git a/source/java/org/alfresco/repo/usage/UserUsageBootstrapJob.java b/source/java/org/alfresco/repo/usage/UserUsageBootstrapJob.java
new file mode 100644
index 0000000000..f5488b6dd5
--- /dev/null
+++ b/source/java/org/alfresco/repo/usage/UserUsageBootstrapJob.java
@@ -0,0 +1,56 @@
+/*
+ * 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 org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * Bootstraps user's content usage. This job is performed once at startup.
+ *
+ * If usages are enabled (as specified by 'system.usages.enabled=true' repository property) then will calculate
+ * usages for all users that have no current usage.
+ *
+ * If usages are disabled (as specified by 'system.usages.enabled=false' repository property) then will clear
+ * current usages for all users.
+ */
+public class UserUsageBootstrapJob implements Job
+{
+ private static final String KEY_COMPONENT = "userUsageBootstrapComponent";
+
+ public void execute(JobExecutionContext context) throws JobExecutionException
+ {
+ JobDataMap jobData = context.getJobDetail().getJobDataMap();
+ UserUsageTrackingComponent usageComponent = (UserUsageTrackingComponent) jobData.get(KEY_COMPONENT);
+ if (usageComponent == null)
+ {
+ throw new JobExecutionException("Missing job data: " + KEY_COMPONENT);
+ }
+ // perform the content usage calculations
+ usageComponent.execute();
+ }
+}
diff --git a/source/java/org/alfresco/repo/usage/UserUsageCollapseJob.java b/source/java/org/alfresco/repo/usage/UserUsageCollapseJob.java
new file mode 100644
index 0000000000..7ad8edb4c5
--- /dev/null
+++ b/source/java/org/alfresco/repo/usage/UserUsageCollapseJob.java
@@ -0,0 +1,50 @@
+/*
+ * 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 org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+/**
+ * Collapses user's content usage delta. This is performed as a regular background job.
+ */
+public class UserUsageCollapseJob implements Job
+{
+ private static final String KEY_COMPONENT = "userUsageCollapseComponent";
+
+ public void execute(JobExecutionContext context) throws JobExecutionException
+ {
+ JobDataMap jobData = context.getJobDetail().getJobDataMap();
+ UserUsageTrackingComponent usageComponent = (UserUsageTrackingComponent) jobData.get(KEY_COMPONENT);
+ if (usageComponent == null)
+ {
+ throw new JobExecutionException("Missing job data: " + KEY_COMPONENT);
+ }
+ // perform the content usage calculations
+ usageComponent.execute();
+ }
+}
diff --git a/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java
new file mode 100644
index 0000000000..08a266b86e
--- /dev/null
+++ b/source/java/org/alfresco/repo/usage/UserUsageTrackingComponent.java
@@ -0,0 +1,328 @@
+/*
+ * 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.util.Collection;
+import java.util.HashSet;
+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.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.TransactionServiceImpl;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+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.UsageService;
+import org.alfresco.service.namespace.QName;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * User Usage Tracking Component - to allow user usages to be collapsed or re-calculated
+ *
+ * - used by UserUsageCollapseJob to collapse usage deltas.
+ * - used by UserUsageBootstrapJob to either clear all usages or (re-)calculate all missing usages.
+ */
+public class UserUsageTrackingComponent
+{
+ private static Log logger = LogFactory.getLog(UserUsageTrackingComponent.class);
+
+ private static boolean busy = false;
+
+ private boolean bootstrap = false;
+
+ private NodeDaoService nodeDaoService;
+ private TransactionServiceImpl transactionService;
+ private ContentUsageImpl contentUsageImpl;
+
+ private PersonService personService;
+ private NodeService nodeService;
+ private UsageService usageService;
+
+ private boolean enabled = true;
+
+
+ public void setNodeDaoService(NodeDaoService nodeDaoService)
+ {
+ this.nodeDaoService = nodeDaoService;
+ }
+
+ public void setTransactionService(TransactionServiceImpl transactionService)
+ {
+ this.transactionService = transactionService;
+ }
+
+ public void setContentUsageImpl(ContentUsageImpl contentUsageImpl)
+ {
+ this.contentUsageImpl = contentUsageImpl;
+ }
+
+ public void setBootstrap(boolean bootstrap)
+ {
+ this.bootstrap = bootstrap;
+ }
+
+ public void setPersonService(PersonService personService)
+ {
+ this.personService = personService;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setUsageService(UsageService usageService)
+ {
+ this.usageService = usageService;
+ }
+
+ public void setEnabled(boolean enabled)
+ {
+ this.enabled = enabled;
+ }
+
+
+ public void execute()
+ {
+ try
+ {
+ if (! busy && ! enabled)
+ {
+ busy = true;
+
+ // disabled - remove all usages
+ if (bootstrap == true)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Disabled - clear usages for all users ...");
+ }
+
+ RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
+
+ // wrap to make the request in a transaction
+ RetryingTransactionCallback clearAllUsages = new RetryingTransactionCallback()
+ {
+ public Integer execute() throws Throwable
+ {
+ Set allPeople = personService.getAllPeople();
+
+ for (NodeRef personNodeRef : allPeople)
+ {
+ nodeService.setProperty(personNodeRef, ContentModel.PROP_SIZE_CURRENT, null);
+ usageService.deleteDeltas(personNodeRef);
+ }
+ return allPeople.size();
+ }
+ };
+ // execute in txn
+ int count = txnHelper.doInTransaction(clearAllUsages, false);
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("... cleared usage for " + count + " users");
+ }
+ }
+ }
+ else if (! busy && enabled)
+ {
+ busy = true;
+
+ if (bootstrap == true)
+ {
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("Enabled - calculate usages for all users (without usage) ...");
+ }
+
+ RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
+
+ // wrap to make the request in a transaction
+ RetryingTransactionCallback> getAllPeople = new RetryingTransactionCallback>()
+ {
+ public Set execute() throws Throwable
+ {
+ Set allPeople = personService.getAllPeople();
+ Set userNames = new HashSet();
+
+ for (NodeRef personNodeRef : allPeople)
+ {
+ Long currentUsage = (Long)nodeService.getProperty(personNodeRef, ContentModel.PROP_SIZE_CURRENT);
+ if (currentUsage == null)
+ {
+ String userName = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME);
+ userNames.add(userName);
+ }
+ }
+ return userNames;
+ }
+ };
+ // execute in READ-ONLY txn
+ final Set userNames = txnHelper.doInTransaction(getAllPeople, true);
+
+ for (String userName : userNames)
+ {
+ recalculateUsage(userName);
+ }
+
+ if (logger.isDebugEnabled())
+ {
+ logger.debug("... calculated usage for " + userNames.size() + " users");
+ }
+ }
+ else
+ {
+ // Collapse usage deltas (if a person has initial usage set)
+ RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
+
+ // wrap to make the request in a transaction
+ RetryingTransactionCallback