diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-action.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-action.lib.ftl new file mode 100644 index 0000000000..a141834873 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-action.lib.ftl @@ -0,0 +1,14 @@ +<#-- Renders the details of a running action. --> +<#macro runningActionJSON action> +<#escape x as jsonUtils.encodeJSONString(x)> + { + "actionId": "${action.id}", + "actionType": "${action.type}", + "actionInstance": "${action.instance?string}", + "actionNodeRef": <#if action.nodeRef??>"${action.nodeRef.nodeRef}"<#else>null, + "startedAt": "${action.startedAt}", + "cancelRequested": "${action.cancelRequested?string}", + "details": "${"/api/running-action/" + action.key}", + } + + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.get.desc.xml new file mode 100644 index 0000000000..9430524fb4 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.get.desc.xml @@ -0,0 +1,10 @@ + + List Running Actions + + Returns (limited) details on all currently running actions. + + /api/running-actions?type={type?}&nodeRef={nodeRef?} + + admin + required + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.get.json.ftl new file mode 100644 index 0000000000..f371f36b3e --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.get.json.ftl @@ -0,0 +1,10 @@ +<#import "running-action.lib.ftl" as actionLib /> +{ + "data": + [ + <#list runningActions as action> + <@actionLib.runningActionJSON action=action /> + <#if action_has_next>, + + ] +} diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index cf27e0cfb2..2943bea73f 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -839,9 +839,9 @@ parent="abstractAuditWebScript"> - + - + + + + + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/action/RunningActionModelBuilder.java b/source/java/org/alfresco/repo/web/scripts/action/RunningActionModelBuilder.java index 440dc6bfef..57e69b8218 100644 --- a/source/java/org/alfresco/repo/web/scripts/action/RunningActionModelBuilder.java +++ b/source/java/org/alfresco/repo/web/scripts/action/RunningActionModelBuilder.java @@ -18,9 +18,19 @@ */ package org.alfresco.repo.web.scripts.action; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionTrackingService; +import org.alfresco.service.cmr.action.ExecutionDetails; +import org.alfresco.service.cmr.action.ExecutionSummary; +import org.alfresco.service.cmr.replication.ReplicationDefinition; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.util.ISO8601DateFormat; /** * Builds up models for running actions @@ -33,6 +43,15 @@ public class RunningActionModelBuilder protected static final String MODEL_DATA_ITEM = "runningAction"; protected static final String MODEL_DATA_LIST = "runningActions"; + protected static final String ACTION_ID = "id"; + protected static final String ACTION_TYPE = "type"; + protected static final String ACTION_INSTANCE = "instance"; + protected static final String ACTION_NODE_REF = "nodeRef"; + protected static final String ACTION_STARTED_AT = "startedAt"; + protected static final String ACTION_RUNNING_ON = "runningOn"; + protected static final String ACTION_CANCEL_REQUESTED = "cancelRequested"; + protected static final String ACTION_KEY = "key"; + protected NodeService nodeService; protected ActionService actionService; @@ -45,4 +64,40 @@ public class RunningActionModelBuilder this.actionService = actionService; this.actionTrackingService = actionTrackingService; } + + + /** + * Build a model containing a list of running actions for the given + * list of Running Actions + */ + protected Map buildSimpleList(List runningActions) + { + List> models = new ArrayList>(); + + for(ExecutionSummary summary : runningActions) { + ExecutionDetails details = actionTrackingService.getExecutionDetails(summary); + + // Only record if still running - may have finished + // between getting the list and now + if(details != null) { + Map ram = new HashMap(); + ram.put(ACTION_ID, summary.getActionId()); + ram.put(ACTION_TYPE, summary.getActionType()); + ram.put(ACTION_INSTANCE, summary.getExecutionInstance()); + ram.put(ACTION_KEY, AbstractActionWebscript.getRunningId(summary)); + + ram.put(ACTION_NODE_REF, details.getPersistedActionRef()); + ram.put(ACTION_STARTED_AT, ISO8601DateFormat.format(details.getStartedAt())); + ram.put(ACTION_RUNNING_ON, details.getRunningOn()); + ram.put(ACTION_CANCEL_REQUESTED, details.isCancelRequested()); + + models.add(ram); + } + } + + // Finish up + Map model = new HashMap(); + model.put(MODEL_DATA_LIST, models); + return model; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/action/RunningActionRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/action/RunningActionRestApiTest.java new file mode 100644 index 0000000000..795593e20a --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/action/RunningActionRestApiTest.java @@ -0,0 +1,439 @@ +/* + * 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.web.scripts.action; + +import javax.transaction.UserTransaction; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionImpl; +import org.alfresco.repo.cache.EhCacheAdapter; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.person.TestPersonManager; +import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.service.cmr.action.ActionStatus; +import org.alfresco.service.cmr.action.ActionTrackingService; +import org.alfresco.service.cmr.action.ExecutionDetails; +import org.alfresco.service.cmr.action.ExecutionSummary; +import org.alfresco.service.cmr.replication.ReplicationDefinition; +import org.alfresco.service.cmr.replication.ReplicationService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.GUID; +import org.alfresco.util.ISO8601DateFormat; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.context.ApplicationContext; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PutRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.DeleteRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.Response; + +/** + * Tests for the Running Action Webscripts + * @author Nick Burch + */ +public class RunningActionRestApiTest extends BaseWebScriptTest +{ + private static final String URL_RUNNING_ACTION = "api/running-action/"; + private static final String URL_RUNNING_ACTIONS = "api/running-actions"; + private static final String URL_RUNNING_REPLICATION_ACTIONS = "api/running-replication-actions/"; + + private static final String JSON = "application/json"; + + private static final String USER_NORMAL = "Normal" + GUID.generate(); + + private NodeService nodeService; + private TestPersonManager personManager; + private ReplicationService replicationService; + private TransactionService transactionService; + private ActionTrackingService actionTrackingService; + private EhCacheAdapter executingActionsCache; + + private Repository repositoryHelper; + private NodeRef dataDictionary; + + public void testRunningActionsGet() throws Exception + { + Response response; + + + // Not allowed if you're not an admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getGuestUserName()); + response = sendRequest(new GetRequest(URL_RUNNING_ACTIONS), Status.STATUS_UNAUTHORIZED); + assertEquals(Status.STATUS_UNAUTHORIZED, response.getStatus()); + + AuthenticationUtil.setFullyAuthenticatedUser(USER_NORMAL); + response = sendRequest(new GetRequest(URL_RUNNING_ACTIONS), Status.STATUS_UNAUTHORIZED); + assertEquals(Status.STATUS_UNAUTHORIZED, response.getStatus()); + + + // If nothing running, you don't get anything back + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + response = sendRequest(new GetRequest(URL_RUNNING_ACTIONS), 200); + assertEquals(Status.STATUS_OK, response.getStatus()); + + String jsonStr = response.getContentAsString(); + JSONObject json = new JSONObject(jsonStr); + JSONArray results = json.getJSONArray("data"); + assertNotNull(results); + assertEquals(0, results.length()); + + + // Add a running action, it should show up + ReplicationDefinition rd = replicationService.createReplicationDefinition("Test1", "Testing"); + replicationService.saveReplicationDefinition(rd); + actionTrackingService.recordActionExecuting(rd); + String id = rd.getId(); + String instance = Integer.toString( ((ActionImpl)rd).getExecutionInstance() ); + String startedAt = ISO8601DateFormat.format(rd.getExecutionStartDate()); + + response = sendRequest(new GetRequest(URL_RUNNING_ACTIONS), 200); + assertEquals(Status.STATUS_OK, response.getStatus()); + + jsonStr = response.getContentAsString(); + json = new JSONObject(jsonStr); + results = json.getJSONArray("data"); + assertNotNull(results); + assertEquals(1, results.length()); + + JSONObject jsonRD = (JSONObject)results.get(0); + assertNotNull(jsonRD); + assertEquals(id, jsonRD.get("actionId")); + assertEquals("replicationActionExecutor", jsonRD.get("actionType")); + assertEquals(instance, jsonRD.get("actionInstance")); + assertEquals(rd.getNodeRef().toString(), jsonRD.get("actionNodeRef")); + assertEquals(startedAt, jsonRD.get("startedAt")); + assertEquals(false, jsonRD.getBoolean("cancelRequested")); + assertEquals("/" + URL_RUNNING_ACTION + "replicationActionExecutor=" + + id + "=" + instance, jsonRD.get("details")); + + + // Ensure we didn't get any unexpected data back, + // only the keys we should have done + JSONArray keys = jsonRD.names(); + for(int i=0; i)appContext.getBean("executingActionsSharedCache"); + + MutableAuthenticationService authenticationService = (MutableAuthenticationService)appContext.getBean("AuthenticationService"); + PersonService personService = (PersonService)appContext.getBean("PersonService"); + personManager = new TestPersonManager(authenticationService, personService, nodeService); + + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + personManager.createPerson(USER_NORMAL); + + // Ensure we start with no replication definitions + // (eg another test left them behind) + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + for(ReplicationDefinition rd : replicationService.loadReplicationDefinitions()) { + replicationService.deleteReplicationDefinition(rd); + } + txn.commit(); + + // Grab a reference to the data dictionary + dataDictionary = nodeService.getChildByName( + repositoryHelper.getCompanyHome(), + ContentModel.ASSOC_CONTAINS, + "Data Dictionary" + ); + + AuthenticationUtil.clearCurrentSecurityContext(); + } + + /* (non-Javadoc) + * @see junit.framework.TestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + personManager.clearPeople(); + + // Zap any replication definitions we created + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + for(ReplicationDefinition rd : replicationService.loadReplicationDefinitions()) { + replicationService.deleteReplicationDefinition(rd); + } + AuthenticationUtil.clearCurrentSecurityContext(); + + // Clear out the running actions + for(ExecutionSummary es : actionTrackingService.getAllExecutingActions()) { + executingActionsCache.remove( + AbstractActionWebscript.getRunningId(es) + ); + } + + txn.commit(); + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/action/RunningActionsGet.java b/source/java/org/alfresco/repo/web/scripts/action/RunningActionsGet.java new file mode 100644 index 0000000000..646524048d --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/action/RunningActionsGet.java @@ -0,0 +1,60 @@ +/* + * 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.web.scripts.action; + +import java.util.List; +import java.util.Map; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ExecutionSummary; +import org.alfresco.service.cmr.repository.NodeRef; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * @author Nick Burch + * @since 3.4 + */ +public class RunningActionsGet extends AbstractActionWebscript +{ + @Override + protected Map buildModel( + RunningActionModelBuilder modelBuilder, WebScriptRequest req, + Status status, Cache cache) { + List actions = null; + + // Do they want all actions, or only certain ones? + String type = req.getParameter("type"); + String nodeRef = req.getParameter("nodeRef"); + + if(type != null) { + actions = actionTrackingService.getExecutingActions(type); + } else if(nodeRef != null) { + NodeRef actionNodeRef = new NodeRef(nodeRef); + Action action = runtimeActionService.createAction(actionNodeRef); + actions = actionTrackingService.getExecutingActions(action); + } else { + actions = actionTrackingService.getAllExecutingActions(); + } + + // Build the model list + return modelBuilder.buildSimpleList(actions); + } +} \ No newline at end of file