diff --git a/source/java/org/alfresco/repo/action/ActionImpl.java b/source/java/org/alfresco/repo/action/ActionImpl.java index d8d8e5d337..1ba9ee62d9 100644 --- a/source/java/org/alfresco/repo/action/ActionImpl.java +++ b/source/java/org/alfresco/repo/action/ActionImpl.java @@ -28,6 +28,7 @@ import java.util.Set; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; +import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.repository.NodeRef; /** @@ -104,6 +105,29 @@ public class ActionImpl extends ParameterizedItemImpl implements Action * Action conditions */ private List actionConditions = new ArrayList(); + + /** + * When the action started executing, + * or null if it hasn't yet. + */ + private Date executionStartDate; + + /** + * When the action finished executing, + * or null if it hasn't yet. + */ + private Date executionEndDate; + + /** + * The status of the action's execution + */ + private ActionStatus executionStatus = ActionStatus.New; + + /** + * Why the action failed to execute fully, + * if exists. + */ + private String executionFailureMessage; /** * Constructor @@ -146,6 +170,10 @@ public class ActionImpl extends ParameterizedItemImpl implements Action this.modifier = action.getModifier(); this.nodeRef = action.getNodeRef(); this.title = action.getTitle(); + this.executionStartDate = action.getExecutionStartDate(); + this.executionEndDate = action.getExecutionEndDate(); + this.executionStatus = action.getExecutionStatus(); + this.executionFailureMessage = action.getExecutionFailureMessage(); if (action instanceof ActionImpl) { ActionImpl actionImpl = (ActionImpl) action; @@ -437,5 +465,55 @@ public class ActionImpl extends ParameterizedItemImpl implements Action { getParameterValues().putAll(values); } + + public Date getExecutionStartDate() { + return executionStartDate; + } + /** + * Records the date when the action began execution, + * normally called by the {@link ActionService} when + * it starts running the action. + */ + public void setExecutionStartDate(Date startDate) { + this.executionStartDate = startDate; + } + + public Date getExecutionEndDate() { + return executionEndDate; + } + + /** + * Records the date when the action finished execution, + * normally called by the {@link ActionService} when + * the action completes or fails. + */ + public void setExecutionEndDate(Date endDate) { + this.executionEndDate = endDate; + } + + public ActionStatus getExecutionStatus() { + return executionStatus; + } + + /** + * Updates the current execution status. This is + * normally called by the {@link ActionService} as + * it progresses the Action's execution. + */ + public void setExecutionStatus(ActionStatus status) { + this.executionStatus = status; + } + + public String getExecutionFailureMessage() { + return executionFailureMessage; + } + + /** + * Records the message of the exception which caused the + * Action to fail, if any. + */ + public void setExecutionFailureMessage(String message) { + this.executionFailureMessage = message; + } } diff --git a/source/java/org/alfresco/repo/action/ActionModel.java b/source/java/org/alfresco/repo/action/ActionModel.java index 59d2a04f73..48d0f7401b 100644 --- a/source/java/org/alfresco/repo/action/ActionModel.java +++ b/source/java/org/alfresco/repo/action/ActionModel.java @@ -30,6 +30,10 @@ public interface ActionModel static final QName PROP_ACTION_TITLE = QName.createQName(ACTION_MODEL_URI, "actionTitle"); static final QName PROP_ACTION_DESCRIPTION = QName.createQName(ACTION_MODEL_URI, "actionDescription"); static final QName PROP_EXECUTE_ASYNCHRONOUSLY = QName.createQName(ACTION_MODEL_URI, "executeAsynchronously"); + static final QName PROP_EXECUTION_START_DATE = QName.createQName(ACTION_MODEL_URI, "executionStartDate"); + static final QName PROP_EXECUTION_END_DATE = QName.createQName(ACTION_MODEL_URI, "executionEndDate"); + static final QName PROP_EXECUTION_ACTION_STATUS = QName.createQName(ACTION_MODEL_URI, "executionActionStatus"); + static final QName PROP_EXECUTION_FAILURE_MESSAGE = QName.createQName(ACTION_MODEL_URI, "executionFailureMessage"); static final QName ASSOC_CONDITIONS = QName.createQName(ACTION_MODEL_URI, "conditions"); static final QName ASSOC_COMPENSATING_ACTION = QName.createQName(ACTION_MODEL_URI, "compensatingAction"); diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index e069e47127..3a6802efac 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -46,6 +46,7 @@ import org.alfresco.service.cmr.action.ActionDefinition; import org.alfresco.service.cmr.action.ActionList; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionServiceException; +import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.action.CompositeAction; import org.alfresco.service.cmr.action.CompositeActionCondition; import org.alfresco.service.cmr.action.ParameterConstraint; @@ -879,6 +880,12 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A props.put(ActionModel.PROP_ACTION_TITLE, action.getTitle()); props.put(ActionModel.PROP_ACTION_DESCRIPTION, action.getDescription()); props.put(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY, action.getExecuteAsychronously()); + + props.put(ActionModel.PROP_EXECUTION_START_DATE, action.getExecutionStartDate()); + props.put(ActionModel.PROP_EXECUTION_END_DATE, action.getExecutionEndDate()); + props.put(ActionModel.PROP_EXECUTION_ACTION_STATUS, action.getExecutionStatus()); + props.put(ActionModel.PROP_EXECUTION_FAILURE_MESSAGE, action.getExecutionFailureMessage()); + this.nodeService.setProperties(actionNodeRef, props); // Update the compensating action (model should enforce the singularity @@ -1283,6 +1290,11 @@ public class ActionServiceImpl implements ActionService, RuntimeActionService, A ((ActionImpl) action).setCreatedDate((Date) props.get(ContentModel.PROP_CREATED)); ((ActionImpl) action).setModifier((String) props.get(ContentModel.PROP_MODIFIER)); ((ActionImpl) action).setModifiedDate((Date) props.get(ContentModel.PROP_MODIFIED)); + + ((ActionImpl) action).setExecutionStartDate((Date) props.get(ActionModel.PROP_EXECUTION_START_DATE)); + ((ActionImpl) action).setExecutionEndDate((Date) props.get(ActionModel.PROP_EXECUTION_END_DATE)); + ((ActionImpl) action).setExecutionStatus(ActionStatus.valueOf(props.get(ActionModel.PROP_EXECUTION_ACTION_STATUS))); + ((ActionImpl) action).setExecutionFailureMessage((String) props.get(ActionModel.PROP_EXECUTION_FAILURE_MESSAGE)); // Get the compensating action List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, diff --git a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java index 0dd84d3129..30f351265d 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImplTest.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImplTest.java @@ -19,8 +19,7 @@ package org.alfresco.repo.action; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -45,6 +44,7 @@ import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionConditionDefinition; import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.action.CompositeAction; import org.alfresco.service.cmr.action.CompositeActionCondition; import org.alfresco.service.cmr.action.ParameterDefinition; @@ -55,7 +55,6 @@ import org.alfresco.service.cmr.repository.NodeService; 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.alfresco.util.BaseAlfrescoSpringTest; /** @@ -1140,6 +1139,83 @@ public class ActionServiceImplTest extends BaseAlfrescoSpringTest } + /** + * Tests that we can read, save, load etc the various + * execution related details such as started at, + * ended at, status and exception + */ + public void testExecutionTrackingDetails() { + Action action = this.actionService.createAction(AddFeaturesActionExecuter.NAME); + String actionId = action.getId(); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + + // Save and load, details shouldn't have changed + this.actionService.saveAction(this.nodeRef, action); + action = (Action)this.actionService.getAction(this.nodeRef, actionId); + + assertNull(action.getExecutionStartDate()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + assertEquals(ActionStatus.New, action.getExecutionStatus()); + + + // Set some details, ensure they survive a save/load + ((ActionImpl)action).setExecutionStatus(ActionStatus.Running); + ((ActionImpl)action).setExecutionStartDate(new Date(12345)); + + this.actionService.saveAction(this.nodeRef, action); + action = (Action)this.actionService.getAction(this.nodeRef, actionId); + + assertEquals(ActionStatus.Running, action.getExecutionStatus()); + assertEquals(12345, action.getExecutionStartDate().getTime()); + assertNull(action.getExecutionEndDate()); + assertNull(action.getExecutionFailureMessage()); + + + // Set the rest, and change some, ensure they survive a save/load + ((ActionImpl)action).setExecutionStatus(ActionStatus.Failed); + ((ActionImpl)action).setExecutionStartDate(new Date(123450)); + ((ActionImpl)action).setExecutionEndDate(new Date(123455)); + ((ActionImpl)action).setExecutionFailureMessage("Testing"); + + this.actionService.saveAction(this.nodeRef, action); + action = (Action)this.actionService.getAction(this.nodeRef, actionId); + + assertEquals(ActionStatus.Failed, action.getExecutionStatus()); + assertEquals(123450, action.getExecutionStartDate().getTime()); + assertEquals(123455, action.getExecutionEndDate().getTime()); + assertEquals("Testing", action.getExecutionFailureMessage()); + + + // Unset a few, ensure they survive a save/load + ((ActionImpl)action).setExecutionStatus(null); + ((ActionImpl)action).setExecutionStartDate(new Date(123450)); + ((ActionImpl)action).setExecutionFailureMessage(null); + + this.actionService.saveAction(this.nodeRef, action); + action = (Action)this.actionService.getAction(this.nodeRef, actionId); + + assertEquals(ActionStatus.New, action.getExecutionStatus()); // Default + assertEquals(123450, action.getExecutionStartDate().getTime()); + assertEquals(123455, action.getExecutionEndDate().getTime()); + assertEquals(null, action.getExecutionFailureMessage()); + } + + /** + * Tests that when we run an action, either + * synchronously or asynchronously, with it + * working or failing, that the action execution + * service correctly sets the flags + */ + public void testExecutionTrackingOnExecution() { + // TODO + } + /** * This class is only used during JUnit testing. * diff --git a/source/java/org/alfresco/repo/action/actionModel.xml b/source/java/org/alfresco/repo/action/actionModel.xml index 3340d82493..84c1805525 100644 --- a/source/java/org/alfresco/repo/action/actionModel.xml +++ b/source/java/org/alfresco/repo/action/actionModel.xml @@ -15,6 +15,20 @@ + + + + + New + Pending + Running + Completed + Cancelled + Failed + + + + @@ -64,6 +78,25 @@ d:text false + + d:date + false + + + d:date + false + + + d:text + false + + + + + + d:text + false + diff --git a/source/java/org/alfresco/service/cmr/action/Action.java b/source/java/org/alfresco/service/cmr/action/Action.java index c6ce1cee95..ec3d091a3a 100644 --- a/source/java/org/alfresco/service/cmr/action/Action.java +++ b/source/java/org/alfresco/service/cmr/action/Action.java @@ -208,4 +208,38 @@ public interface Action extends ParameterizedItem * @param values A map of values to be added */ void addParameterValues(Map values); + + /** + * Gets the date that the action (last) began executing at. + * Null if the action has not yet been run. + * For a saved action, this will be the last time at ran. + * @return The date the action (last) began executing at, or null. + */ + Date getExecutionStartDate(); + + /** + * Gets the date that the action (last) finished + * execution at. + * Null if the action has not yet been run, or is + * currently running. + * For a saved action, this will normally be the last + * time it finished running. + * @return The date the action last finished exeucting at, or null. + */ + Date getExecutionEndDate(); + + /** + * Gets the current execution status of the action, + * such as Running or Completed. + * @return The current execution status + */ + ActionStatus getExecutionStatus(); + + /** + * Gets the message of the exception which caused the + * Action execution failure, or null if the Action + * hasn't failed / has been retried. + * @return The exception message, if the action has failed + */ + String getExecutionFailureMessage(); } diff --git a/source/java/org/alfresco/service/cmr/action/ActionStatus.java b/source/java/org/alfresco/service/cmr/action/ActionStatus.java new file mode 100644 index 0000000000..38e1e2f7b8 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/action/ActionStatus.java @@ -0,0 +1,69 @@ +/* + * 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.service.cmr.action; + +import java.io.Serializable; + + + +/** + * The various states an Action can be in. + * + * @author Nick Burch + */ +public enum ActionStatus +{ + /** + * A new Action, which has never been run + */ + New, + /** + * An Action which has been scheduled for + * Asynchronous execution, but not yet run. + */ + Pending, + /** + * Indicates that the Action is currently being + * executed by the {@link ActionService} + */ + Running, + /** + * The Action was run without error + */ + Completed, + /** + * The Action, which must implement + * {@link CancellableAction}, detected that a + * cancellation was requested and cancelled itself. + */ + Cancelled, + /** + * The Action failed to run to completion. Call + * {@link Action#getExecutionFailureCause()} to find + * out why. + */ + Failed + ; + + public static ActionStatus valueOf(Serializable s) + { + if(s == null) return New; + return valueOf((String)s); + } +} diff --git a/source/java/org/alfresco/service/cmr/action/CancellableAction.java b/source/java/org/alfresco/service/cmr/action/CancellableAction.java new file mode 100644 index 0000000000..9890bf5a1e --- /dev/null +++ b/source/java/org/alfresco/service/cmr/action/CancellableAction.java @@ -0,0 +1,31 @@ +/* + * 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.service.cmr.action; + +/** + * A marker interface that forms part of the Cancel Action contract. + * An action that implements this interface commits to periodically + * asking the {@link ActionService} if a cancel of it has been + * requested, and orderly terminating itself if so. + * + * @author Nick Burch + */ +public interface CancellableAction extends Action +{ +}