diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.post.desc.xml new file mode 100644 index 0000000000..00a47a3da6 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.post.desc.xml @@ -0,0 +1,11 @@ + + Start a new Action Executing + + Starts the execution of a new action, and returns its running details + + /api/running-actions?nodeRef={nodeRef?} + + admin + + none + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.post.json.ftl new file mode 100644 index 0000000000..b9d0a17efb --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-actions.post.json.ftl @@ -0,0 +1,2 @@ +<#import "running-action.lib.ftl" as actionLib /> +<@actionLib.runningActionJSON action=runningAction /> diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-replication-actions.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-replication-actions.post.desc.xml new file mode 100644 index 0000000000..8952e774b2 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-replication-actions.post.desc.xml @@ -0,0 +1,11 @@ + + Start a new Replication Action Executing + + Starts the execution of a replication action, and returns its running details + + /api/running-replication-actions?name={name?} + + admin + + none + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-replication-actions.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-replication-actions.post.json.ftl new file mode 100644 index 0000000000..b9d0a17efb --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/action/running-replication-actions.post.json.ftl @@ -0,0 +1,2 @@ +<#import "running-action.lib.ftl" as actionLib /> +<@actionLib.runningActionJSON action=runningAction /> diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index ffaaf836d7..e6dc96dbb9 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -907,6 +907,13 @@ + + + + + + + + + + + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/action/AbstractActionWebscript.java b/source/java/org/alfresco/repo/web/scripts/action/AbstractActionWebscript.java index 522d628044..aff73c2beb 100644 --- a/source/java/org/alfresco/repo/web/scripts/action/AbstractActionWebscript.java +++ b/source/java/org/alfresco/repo/web/scripts/action/AbstractActionWebscript.java @@ -21,9 +21,12 @@ package org.alfresco.repo.web.scripts.action; import java.util.Map; import java.util.NoSuchElementException; +import org.alfresco.repo.action.ActionImpl; import org.alfresco.repo.action.ActionTrackingServiceImpl; import org.alfresco.repo.action.RuntimeActionService; +import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.action.ActionStatus; import org.alfresco.service.cmr.action.ActionTrackingService; import org.alfresco.service.cmr.action.ExecutionSummary; import org.alfresco.service.cmr.repository.NodeService; @@ -78,7 +81,7 @@ public abstract class AbstractActionWebscript extends DeclarativeWebScript WebScriptRequest req, Status status, Cache cache ); - + /** * Takes a running action ID, and returns an @@ -91,6 +94,25 @@ public abstract class AbstractActionWebscript extends DeclarativeWebScript return WrappedActionTrackingService.getSummaryFromKey(key); } + /** + * Returns the ExecutionSummary for the given action if it + * is currently executing, or null if it isn't + */ + public static ExecutionSummary getSummaryFromAction(Action action) + { + // Is it running? + if(action.getExecutionStatus() == ActionStatus.Running) { + return WrappedActionTrackingService.buildExecutionSummary(action); + } + // Has it been given a execution id? + // (eg has already finished, but this one was run) + if( ((ActionImpl)action).getExecutionInstance() != -1 ) { + return WrappedActionTrackingService.buildExecutionSummary(action); + } + // Not running, and hasn't run, we can't help + return null; + } + /** * Returns the running action ID for the given * ExecutionSummary @@ -112,6 +134,11 @@ public abstract class AbstractActionWebscript extends DeclarativeWebScript return ActionTrackingServiceImpl.generateCacheKey(summary); } + protected static ExecutionSummary buildExecutionSummary(Action action) + { + return ActionTrackingServiceImpl.buildExecutionSummary(action); + } + private static ExecutionSummary getSummaryFromKey(String key) { try { diff --git a/source/java/org/alfresco/repo/web/scripts/action/AbstractExecuteActionWebscript.java b/source/java/org/alfresco/repo/web/scripts/action/AbstractExecuteActionWebscript.java new file mode 100644 index 0000000000..357f8bfec8 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/action/AbstractExecuteActionWebscript.java @@ -0,0 +1,113 @@ +/* + * 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.Map; + +import javax.transaction.UserTransaction; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionStatus; +import org.alfresco.service.cmr.action.ExecutionSummary; +import org.alfresco.service.transaction.TransactionService; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; + + +/** + * @author Nick Burch + * @since 3.4 + */ +public abstract class AbstractExecuteActionWebscript extends AbstractActionWebscript +{ + protected TransactionService transactionService; + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + protected Map buildModel( + RunningActionModelBuilder modelBuilder, + WebScriptRequest req, + Status status, Cache cache) + { + try { + // Start our transaction + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + // Have the action to run be identified + Action action = identifyAction(req, status, cache); + if(action == null) { + txn.rollback(); + throw new WebScriptException( + Status.STATUS_NOT_FOUND, + "No Running Action found with the supplied details" + ); + } + + // Ask for it to be run in the background + actionService.executeAction( + action, null, + false, true + ); + + // Have it begin + txn.commit(); + + // TODO Update this after changes to the + // action tracking service for pending actions + // have been made + + // Wait up to 5 seconds for it to kick off + long beganWaitingAt = System.currentTimeMillis(); + while(beganWaitingAt + 5000 > System.currentTimeMillis() && + action.getExecutionStatus() == ActionStatus.Pending) + { + try { + Thread.sleep(50); + } catch(InterruptedException e) {} + } + + // Return the details if we can +System.err.println(action); +System.err.println(action.getExecutionStatus()); + ExecutionSummary summary = getSummaryFromAction(action); + if(summary == null) { + throw new WebScriptException( + Status.STATUS_EXPECTATION_FAILED, + "Action failed to start in the required timeframe" + ); + } + + return modelBuilder.buildSimpleModel(summary); + } catch(Exception e) { + // Transaction broke + throw new RuntimeException(e); + } + } + + protected abstract Action identifyAction( + WebScriptRequest req, + Status status, Cache cache + ); +} \ 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 index 5ffe1009bc..8cbd88640a 100644 --- a/source/java/org/alfresco/repo/web/scripts/action/RunningActionRestApiTest.java +++ b/source/java/org/alfresco/repo/web/scripts/action/RunningActionRestApiTest.java @@ -738,6 +738,92 @@ public class RunningActionRestApiTest extends BaseWebScriptTest assertEquals(true, actionTrackingService.isCancellationRequested(rd)); } + /** + * TODO Fix this up after the changes to the action tracking + * service for pending actions have been made + */ + public void DISABLEDtestRunningActionsPost() throws Exception + { + Response response; + + + // Not allowed if you're not an admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getGuestUserName()); + response = sendRequest(new PostRequest(URL_RUNNING_ACTIONS, "{}", JSON), Status.STATUS_UNAUTHORIZED); + assertEquals(Status.STATUS_UNAUTHORIZED, response.getStatus()); + + AuthenticationUtil.setFullyAuthenticatedUser(USER_NORMAL); + response = sendRequest(new PostRequest(URL_RUNNING_ACTIONS, "{}", JSON), Status.STATUS_UNAUTHORIZED); + assertEquals(Status.STATUS_UNAUTHORIZED, response.getStatus()); + + + // If no noderef supplied, will get an error + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + response = sendRequest(new PostRequest(URL_RUNNING_ACTIONS, "{}", JSON), Status.STATUS_BAD_REQUEST); + assertEquals(Status.STATUS_BAD_REQUEST, response.getStatus()); + + + // Add a running action + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + + ReplicationDefinition rd = replicationService.createReplicationDefinition("Test1", "Testing"); + replicationService.saveReplicationDefinition(rd); + String id = rd.getId(); + + txn.commit(); + + + // Ask for it to be started + // (It should start but fail due to missing definition parts) + JSONObject json = new JSONObject(); + json.put("nodeRef", rd.getNodeRef().toString()); + + response = sendRequest(new PostRequest(URL_RUNNING_ACTIONS, json.toString(), JSON), Status.STATUS_OK); + assertEquals(Status.STATUS_OK, response.getStatus()); + + // Check we got back some details on it + String jsonStr = response.getContentAsString(); + JSONObject jsonRD = new JSONObject(jsonStr); + assertNotNull(jsonRD); + + assertEquals(id, jsonRD.get("actionId")); + assertEquals(ReplicationDefinitionImpl.EXECUTOR_NAME, jsonRD.get("actionType")); + assertEquals(rd.getNodeRef().toString(), jsonRD.get("actionNodeRef")); + assertEquals(false, jsonRD.getBoolean("cancelRequested")); + + // Wait a bit for it to be run + fail + // Check that it failed due to insufficient definition options + for(int i=0; i<50; i++) { + txn = transactionService.getUserTransaction(); + txn.begin(); + rd = replicationService.loadReplicationDefinition("Test1"); + txn.commit(); + + if(rd.getExecutionStatus() == ActionStatus.New || + rd.getExecutionStatus() == ActionStatus.Pending || + rd.getExecutionStatus() == ActionStatus.Running) { + try { + Thread.sleep(100); + } catch(InterruptedException e) {} + } else { + break; + } + } + assertEquals(ActionStatus.Failed, rd.getExecutionStatus()); + + + // Ensure you can't start with an invalid nodeRef + json = new JSONObject(); + json.put("nodeRef", "XX"+rd.getNodeRef().toString()+"ZZ"); + + response = sendRequest(new PostRequest(URL_RUNNING_ACTIONS, json.toString(), JSON), Status.STATUS_BAD_REQUEST); + assertEquals(Status.STATUS_BAD_REQUEST, response.getStatus()); + } + + + // TODO - Running Replication Actions POST unit tests + @Override protected void setUp() throws Exception diff --git a/source/java/org/alfresco/repo/web/scripts/action/RunningActionsPost.java b/source/java/org/alfresco/repo/web/scripts/action/RunningActionsPost.java new file mode 100644 index 0000000000..c9d15c4503 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/action/RunningActionsPost.java @@ -0,0 +1,68 @@ +/* + * 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.io.IOException; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * @author Nick Burch + * @since 3.4 + */ +public class RunningActionsPost extends AbstractExecuteActionWebscript +{ + @Override + protected Action identifyAction(WebScriptRequest req, Status status, + Cache cache) { + // Which action did they ask for? + String nodeRef = req.getParameter("nodeRef"); + if(nodeRef == null) { + try { + JSONObject json = new JSONObject(new JSONTokener(req.getContent().getContent())); + if(! json.has("nodeRef")) { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not find required 'nodeRef' parameter"); + } + nodeRef = json.getString("nodeRef"); + } + catch (IOException iox) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from request.", iox); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from request.", je); + } + } + + // Load the specified action + NodeRef actionNodeRef = new NodeRef(nodeRef); + Action action = runtimeActionService.createAction(actionNodeRef); + + return action; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/action/RunningReplicationActionsPost.java b/source/java/org/alfresco/repo/web/scripts/action/RunningReplicationActionsPost.java new file mode 100644 index 0000000000..0825c632dc --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/action/RunningReplicationActionsPost.java @@ -0,0 +1,75 @@ +/* + * 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.io.IOException; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.replication.ReplicationDefinition; +import org.alfresco.service.cmr.replication.ReplicationService; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.springframework.extensions.webscripts.Cache; +import org.springframework.extensions.webscripts.Status; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; + +/** + * @author Nick Burch + * @since 3.4 + */ +public class RunningReplicationActionsPost extends AbstractExecuteActionWebscript +{ + private ReplicationService replicationService; + + @Override + protected Action identifyAction(WebScriptRequest req, Status status, + Cache cache) { + // Which action did they ask for? + String name = req.getParameter("name"); + if(name == null) { + try { + JSONObject json = new JSONObject(new JSONTokener(req.getContent().getContent())); + if(! json.has("name")) { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not find required 'name' parameter"); + } + name = json.getString("name"); + } + catch (IOException iox) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from request.", iox); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from request.", je); + } + } + + // Load the specified replication definition + ReplicationDefinition replicationDefinition = + replicationService.loadReplicationDefinition(name); + return replicationDefinition; + } + + public void setReplicationService(ReplicationService replicationService) + { + this.replicationService = replicationService; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/replication/ReplicationModelBuilder.java b/source/java/org/alfresco/repo/web/scripts/replication/ReplicationModelBuilder.java index 5b70be6575..a04e2e9a72 100644 --- a/source/java/org/alfresco/repo/web/scripts/replication/ReplicationModelBuilder.java +++ b/source/java/org/alfresco/repo/web/scripts/replication/ReplicationModelBuilder.java @@ -259,12 +259,17 @@ public class ReplicationModelBuilder } // Use the details of the running copy + // TODO Update this following pending actions changes if(details.isCancelRequested()) { model.put(DEFINITION_STATUS, "CancelRequested"); } else { model.put(DEFINITION_STATUS, "Running"); } - model.put(DEFINITION_STARTED_AT, ISO8601DateFormat.format(details.getStartedAt())); + if(details.getStartedAt() != null) { + model.put(DEFINITION_STARTED_AT, ISO8601DateFormat.format(details.getStartedAt())); + } else { + model.put(DEFINITION_STARTED_AT, null); + } model.put(DEFINITION_ENDED_AT, null); model.put(DEFINITION_RUNNING_ACTION_ID, AbstractActionWebscript.getRunningId(details.getExecutionSummary()));