/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package org.alfresco.repo.action;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;
import org.alfresco.repo.cache.EhCacheAdapter;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionStatus;
import org.alfresco.service.cmr.action.ActionTrackingService;
import org.alfresco.service.cmr.action.CancellableAction;
import org.alfresco.service.cmr.action.ExecutionDetails;
import org.alfresco.service.cmr.action.ExecutionSummary;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Action execution tracking service implementation
*
* @author Nick Burch
*/
public class ActionTrackingServiceImpl implements ActionTrackingService
{
/**
* The logger
*/
private static Log logger = LogFactory.getLog(ActionTrackingServiceImpl.class);
private EhCacheAdapter executingActionsCache;
private TransactionService transactionService;
private RuntimeActionService runtimeActionService;
/**
* Doesn't need to be cluster unique, is just used
* to try to reduce the chance of clashes in the
* quickest and easiest way.
*/
private short nextExecutionId = 1;
/** How we separate bits of the cache key */
private static final char cacheKeyPartSeparator = '=';
/**
* Set the transaction service
*
* @param transactionService the transaction service
*/
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
/**
* Set the runtime action service
*
* @param runtimeActionService the runtime action service
*/
public void setRuntimeActionService(RuntimeActionService runtimeActionService)
{
this.runtimeActionService = runtimeActionService;
}
/**
* Sets the cache used to store details of
* currently executing actions, cluster wide.
*/
public void setExecutingActionsCache(EhCacheAdapter executingActionsCache)
{
this.executingActionsCache = executingActionsCache;
}
public void recordActionPending(Action action)
{
((ActionImpl)action).setExecutionStatus(ActionStatus.Pending);
}
public void recordActionComplete(Action action)
{
if (logger.isDebugEnabled() == true)
{
logger.debug("Action " + action + " has completed execution");
}
// Mark it as having worked
((ActionImpl)action).setExecutionEndDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Completed);
((ActionImpl)action).setExecutionFailureMessage(null);
if(action.getNodeRef() != null)
{
runtimeActionService.saveActionImpl(action.getNodeRef(), action);
}
// Remove it from the cache, as it's finished
String key = generateCacheKey(action);
executingActionsCache.remove(key);
}
public void recordActionExecuting(Action action)
{
if (logger.isDebugEnabled() == true)
{
logger.debug("Action " + action + " has begun exection");
}
// Mark the action as starting
((ActionImpl)action).setExecutionStartDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Running);
// TODO assign it a (unique) execution ID
// (Keep checking to see if the key is used as we
// increase nextExecutionId until it isn't)
String key = generateCacheKey(action);
// Put it into the cache
ExecutionDetails details = buildExecutionDetails(action);
executingActionsCache.put(key, details);
}
/**
* Schedule the recording of the action failure to occur
* in another transaction
*/
public void recordActionFailure(Action action, Throwable exception)
{
if (logger.isDebugEnabled() == true)
{
logger.debug("Will shortly record failure of action " + action + " due to " + exception.getMessage());
}
((ActionImpl)action).setExecutionEndDate(new Date());
((ActionImpl)action).setExecutionStatus(ActionStatus.Failed);
((ActionImpl)action).setExecutionFailureMessage(exception.getMessage());
// Remove it from the cache, as it's no longer running
String key = generateCacheKey(action);
executingActionsCache.remove(key);
// Do we need to update the persisted details?
if(action.getNodeRef() != null)
{
// Take a local copy of the details
// (That way, if someone has a reference to the
// action and plays with it, we still save the
// correct information)
final String actionId = action.getId();
final Date startedAt = action.getExecutionStartDate();
final Date endedAt = action.getExecutionEndDate();
final String message = action.getExecutionFailureMessage();
final NodeRef actionNode = action.getNodeRef();
// Have the details updated on the action as soon
// as the transaction has finished rolling back
AlfrescoTransactionSupport.bindListener(
new TransactionListenerAdapter() {
public void afterRollback()
{
transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback