mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-16 17:55:15 +00:00
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@8244 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
401 lines
15 KiB
Java
401 lines
15 KiB
Java
/*
|
|
* 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.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.security.authentication.AuthenticationUtil.RunAsWork;
|
|
import org.alfresco.repo.tenant.Tenant;
|
|
import org.alfresco.repo.tenant.TenantDeployerService;
|
|
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 NodeDaoService nodeDaoService;
|
|
private TransactionServiceImpl transactionService;
|
|
private ContentUsageImpl contentUsageImpl;
|
|
|
|
private PersonService personService;
|
|
private NodeService nodeService;
|
|
private UsageService usageService;
|
|
private TenantDeployerService tenantDeployerService;
|
|
|
|
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 setPersonService(PersonService personService)
|
|
{
|
|
this.personService = personService;
|
|
}
|
|
|
|
public void setNodeService(NodeService nodeService)
|
|
{
|
|
this.nodeService = nodeService;
|
|
}
|
|
|
|
public void setUsageService(UsageService usageService)
|
|
{
|
|
this.usageService = usageService;
|
|
}
|
|
|
|
public void setTenantDeployerService(TenantDeployerService tenantDeployerService)
|
|
{
|
|
this.tenantDeployerService = tenantDeployerService;
|
|
}
|
|
|
|
public void setEnabled(boolean enabled)
|
|
{
|
|
this.enabled = enabled;
|
|
}
|
|
|
|
public void execute()
|
|
{
|
|
if (enabled == true)
|
|
{
|
|
if (! busy)
|
|
{
|
|
try
|
|
{
|
|
busy = true;
|
|
|
|
// collapse usages - note: for MT environment, will collapse for all tenants
|
|
collapseUsages();
|
|
}
|
|
finally
|
|
{
|
|
busy = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// called once on startup
|
|
public void bootstrap()
|
|
{
|
|
// default domain
|
|
bootstrapInternal();
|
|
|
|
if (tenantDeployerService.isEnabled())
|
|
{
|
|
List<Tenant> tenants = tenantDeployerService.getAllTenants();
|
|
for (Tenant tenant : tenants)
|
|
{
|
|
AuthenticationUtil.runAs(new RunAsWork<Object>()
|
|
{
|
|
public Object doWork() throws Exception
|
|
{
|
|
bootstrapInternal();
|
|
return null;
|
|
}
|
|
}, tenantDeployerService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenant.getTenantDomain()));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void bootstrapInternal()
|
|
{
|
|
if (! busy)
|
|
{
|
|
try
|
|
{
|
|
busy = true;
|
|
|
|
if (enabled)
|
|
{
|
|
// enabled - calculate missing usages
|
|
calculateMissingUsages();
|
|
}
|
|
else
|
|
{
|
|
// disabled - remove all usages
|
|
clearAllUsages();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
busy = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void clearAllUsages()
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Disabled - clear usages for all users ...");
|
|
}
|
|
|
|
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
|
|
|
// wrap to make the request in a transaction
|
|
RetryingTransactionCallback<Integer> clearAllUsages = new RetryingTransactionCallback<Integer>()
|
|
{
|
|
public Integer execute() throws Throwable
|
|
{
|
|
Set<NodeRef> 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 usages for " + count + " users");
|
|
}
|
|
}
|
|
|
|
public void calculateMissingUsages()
|
|
{
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("Enabled - calculate missing usages ...");
|
|
}
|
|
|
|
RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
|
|
|
// wrap to make the request in a transaction
|
|
RetryingTransactionCallback<Set<String>> getAllPeople = new RetryingTransactionCallback<Set<String>>()
|
|
{
|
|
public Set<String> execute() throws Throwable
|
|
{
|
|
Set<NodeRef> allPeople = personService.getAllPeople();
|
|
Set<String> userNames = new HashSet<String>();
|
|
|
|
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<String> userNames = txnHelper.doInTransaction(getAllPeople, true);
|
|
|
|
for (String userName : userNames)
|
|
{
|
|
recalculateUsage(userName);
|
|
}
|
|
|
|
if (logger.isDebugEnabled())
|
|
{
|
|
logger.debug("... calculated missing usages for " + userNames.size() + " users");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recalculate content usage for given user. Required if upgrading an existing Alfresco, for users that
|
|
* have not had their initial usage calculated. In a future release, could also be called explicitly by
|
|
* a SysAdmin, eg. via a JMX operation.
|
|
*
|
|
* @param userName
|
|
*/
|
|
public void recalculateUsage(final String userName)
|
|
{
|
|
final RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
|
|
|
// wrap to make the request in a transaction
|
|
RetryingTransactionCallback<Long> calculatePersonCurrentUsage = new RetryingTransactionCallback<Long>()
|
|
{
|
|
public Long execute() throws Throwable
|
|
{
|
|
List<String> stores = contentUsageImpl.getStores();
|
|
long totalUsage = 0;
|
|
|
|
for (String store : stores)
|
|
{
|
|
StoreRef storeRef = new StoreRef(store);
|
|
|
|
// get nodes for which user is owner
|
|
Collection<Node> ownerNodes = nodeDaoService.getNodesWithPropertyStringValueForStore(storeRef, ContentModel.PROP_OWNER, userName);
|
|
|
|
for (Node ownerNode : ownerNodes)
|
|
{
|
|
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<Node> 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);
|
|
}
|
|
}
|
|
|
|
return totalUsage;
|
|
}
|
|
};
|
|
// execute in READ-ONLY txn
|
|
final Long currentUsage = txnHelper.doInTransaction(calculatePersonCurrentUsage, true);
|
|
|
|
// wrap to make the request in a transaction
|
|
RetryingTransactionCallback<Object> updatePersonCurrentUsage = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
NodeRef personNodeRef = personService.getPerson(userName);
|
|
contentUsageImpl.setUserStoredUsage(personNodeRef, currentUsage);
|
|
usageService.deleteDeltas(personNodeRef);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// execute in txn
|
|
txnHelper.doInTransaction(updatePersonCurrentUsage, false);
|
|
}
|
|
|
|
/**
|
|
* Collapse usages - note: for MT environment, will collapse all tenants
|
|
*/
|
|
private void collapseUsages()
|
|
{
|
|
// Collapse usage deltas (if a person has initial usage set)
|
|
final RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
|
|
|
|
// wrap to make the request in a transaction
|
|
RetryingTransactionCallback<Object> collapseUsages = new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
// Get distinct candidates
|
|
Set<NodeRef> usageNodeRefs = usageService.getUsageDeltaNodes();
|
|
|
|
for(final NodeRef usageNodeRef : usageNodeRefs)
|
|
{
|
|
AuthenticationUtil.runAs(new RunAsWork<Object>()
|
|
{
|
|
public Object doWork() throws Exception
|
|
{
|
|
QName nodeType = nodeService.getType(usageNodeRef);
|
|
|
|
if (nodeType.equals(ContentModel.TYPE_PERSON))
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}, tenantDeployerService.getDomainUser(AuthenticationUtil.getSystemUserName(), tenantDeployerService.getDomain(usageNodeRef.getStoreRef().getIdentifier())));
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// execute in txn
|
|
txnHelper.doInTransaction(collapseUsages, false);
|
|
}
|
|
}
|