diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/replication/replication-definition.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/replication/replication-definition.lib.ftl new file mode 100644 index 0000000000..5e1d7226ad --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/replication/replication-definition.lib.ftl @@ -0,0 +1,22 @@ +<#-- Renders a simple replication definition. --> +<#macro simpleReplicationDefinitionJSON replicationDefinition> +<#escape x as jsonUtils.encodeJSONString(x)> + { + "name": "${replicationDefinition.name}", + "status" : "${replicationDefinition.status}", + "enabled" : "${replicationDefinition.enabled}", + "details": "${replicationDefinition.details_url}", + } + + +<#-- Renders the details of a replication definition. --> +<#macro replicationDefinitionJSON replicationDefinition> +<#escape x as jsonUtils.encodeJSONString(x)> + { + "name": "${replicationDefinition.name}", + "status" : "${replicationDefinition.status}", + "enabled" : "${replicationDefinition.enabled}", + <#-- TODO The rest of the fields --> + } + + diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/replication/replication-definitions.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/replication/replication-definitions.get.json.ftl index 4179d09dac..d61f68fe19 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/replication/replication-definitions.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/replication/replication-definitions.get.json.ftl @@ -1,4 +1,4 @@ -<#import "simple-replication-definition.lib.ftl" as replicationDefLib /> +<#import "replication-definition.lib.ftl" as replicationDefLib /> { "data": [ diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/replication/simple-replication-definition.lib.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/replication/simple-replication-definition.lib.ftl deleted file mode 100644 index ab04ca5499..0000000000 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/replication/simple-replication-definition.lib.ftl +++ /dev/null @@ -1,11 +0,0 @@ -<#-- Renders a workflow definition. --> -<#macro simpleReplicationDefinitionJSON replicationDefinition> -<#escape x as jsonUtils.encodeJSONString(x)> - { - "name": "${replicationDefinition.name}", - "status" : "${replicationDefinition.status}", - "enabled" : "${replicationDefinition.enabled}", - "details": "${replicationDefinition.details_url}", - } - - diff --git a/source/java/org/alfresco/repo/web/scripts/replication/AbstractReplicationWebscript.java b/source/java/org/alfresco/repo/web/scripts/replication/AbstractReplicationWebscript.java index 35a046696e..4ed15356d5 100644 --- a/source/java/org/alfresco/repo/web/scripts/replication/AbstractReplicationWebscript.java +++ b/source/java/org/alfresco/repo/web/scripts/replication/AbstractReplicationWebscript.java @@ -39,15 +39,6 @@ import org.springframework.extensions.webscripts.WebScriptRequest; */ public abstract class AbstractReplicationWebscript extends DeclarativeWebScript { - protected static final String MODEL_DATA_LIST = "replicationDefinitions"; - - protected static final String DEFINITION_NAME = "name"; - protected static final String DEFINITION_STATUS = "status"; - protected static final String DEFINITION_STARTED_AT = "startedAt"; - protected static final String DEFINITION_ENDED_AT = "endedAt"; - protected static final String DEFINITION_ENABLED = "enabled"; - protected static final String DEFINITION_DETAILS_URL = "details"; - protected NodeService nodeService; protected ReplicationService replicationService; protected ActionTrackingService actionTrackingService; @@ -67,56 +58,18 @@ public abstract class AbstractReplicationWebscript extends DeclarativeWebScript this.actionTrackingService = actionTrackingService; } - protected String getDefinitionDetailsUrl(ReplicationDefinition replicationDefinition) + @Override + protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) { - return "/api/replication-definition/" + replicationDefinition.getReplicationName(); + ReplicationModelBuilder modelBuilder = new ReplicationModelBuilder( + nodeService, replicationService, actionTrackingService + ); + return buildModel(modelBuilder, req, status, cache); } - /** - * Figures out the status that's one of: - * New|Running|CancelRequested|Completed|Failed|Cancelled - * by merging data from the action tracking service. - * Will also set the start and end dates, from either the - * replication definition or action tracking data, depending - * on the status. - */ - protected void setStatus(ReplicationDefinition replicationDefinition, Map model) - { - // Is it currently running? - List executing = - actionTrackingService.getExecutingActions(replicationDefinition); - if(executing.size() == 0) { - // It isn't running, we can use the persisted details - model.put(DEFINITION_STATUS, replicationDefinition.getExecutionStatus().toString()); - model.put(DEFINITION_STARTED_AT, replicationDefinition.getExecutionStartDate()); - model.put(DEFINITION_ENDED_AT, replicationDefinition.getExecutionEndDate()); - return; - } - - // We have at least one copy running - ExecutionSummary es; - if(executing.size() == 1) { - es = executing.get(0); - } else { - // More than one copy, joy - // Go for the lowest execution instance id, so - // we're predictable - es = executing.get(0); - for(ExecutionSummary e : executing) { - if(e.getExecutionInstance() < es.getExecutionInstance()) { - es = e; - } - } - } - - // Update the details based on this - ExecutionDetails details = actionTrackingService.getExecutionDetails(es); - if(details.isCancelRequested()) { - model.put(DEFINITION_STATUS, "CancelRequested"); - } else { - model.put(DEFINITION_STATUS, "Running"); - } - model.put(DEFINITION_STARTED_AT, details.getStartedAt()); - model.put(DEFINITION_ENDED_AT, null); - } + protected abstract Map buildModel( + ReplicationModelBuilder modelBuilder, + WebScriptRequest req, + Status status, Cache cache + ); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/replication/ReplicationDefinitionsGet.java b/source/java/org/alfresco/repo/web/scripts/replication/ReplicationDefinitionsGet.java index 5440cec4a3..e7841f3a3a 100644 --- a/source/java/org/alfresco/repo/web/scripts/replication/ReplicationDefinitionsGet.java +++ b/source/java/org/alfresco/repo/web/scripts/replication/ReplicationDefinitionsGet.java @@ -18,8 +18,6 @@ */ package org.alfresco.repo.web.scripts.replication; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,27 +34,13 @@ import org.springframework.extensions.webscripts.WebScriptRequest; public class ReplicationDefinitionsGet extends AbstractReplicationWebscript { @Override - protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) + protected Map buildModel(ReplicationModelBuilder modelBuilder, + WebScriptRequest req, Status status, Cache cache) { + // Get all the defined replication definitions List definitions = replicationService.loadReplicationDefinitions(); - List> models = new ArrayList>(); - for(ReplicationDefinition rd : definitions) { - Map rdm = new HashMap(); - - // Set the basic details - rdm.put(DEFINITION_NAME, rd.getReplicationName()); - rdm.put(DEFINITION_ENABLED, rd.isEnabled()); - rdm.put(DEFINITION_DETAILS_URL, getDefinitionDetailsUrl(rd)); - - // TODO - Make this more efficient by getting all - // the running instances in one go - setStatus(rd, rdm); - } - - // Finish up - Map model = new HashMap(); - model.put(MODEL_DATA_LIST, models); - return model; + // Have them turned into simple models + return modelBuilder.buildSimpleList(definitions); } } \ 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 new file mode 100644 index 0000000000..634837a582 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/replication/ReplicationModelBuilder.java @@ -0,0 +1,203 @@ +/* + * 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.replication; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.NodeService; + +/** + * Builds up models from ReplicationDefinitions, either + * in summary or detail form. + * + * @author Nick Burch + * @since 3.4 + */ +public class ReplicationModelBuilder +{ + protected static final String MODEL_DATA_LIST = "replicationDefinitions"; + + protected static final String DEFINITION_NAME = "name"; + protected static final String DEFINITION_STATUS = "status"; + protected static final String DEFINITION_STARTED_AT = "startedAt"; + protected static final String DEFINITION_ENDED_AT = "endedAt"; + protected static final String DEFINITION_ENABLED = "enabled"; + protected static final String DEFINITION_DETAILS_URL = "details"; + + protected NodeService nodeService; + protected ReplicationService replicationService; + protected ActionTrackingService actionTrackingService; + + public ReplicationModelBuilder(NodeService nodeService, ReplicationService replicationService, + ActionTrackingService actionTrackingService) + { + this.nodeService = nodeService; + this.replicationService = replicationService; + this.actionTrackingService = actionTrackingService; + } + + + /** + * Build a model containing a list of simple definitions for the given + * list of Replication Definitions. + */ + protected Map buildSimpleList(List replicationDefinitions) + { + List> models = new ArrayList>(); + + // Only bother looking up the execution status if we + // have some Replication Definitions to render + if(replicationDefinitions.size() > 0) { + List executing = + actionTrackingService.getExecutingActions(replicationDefinitions.get(0).getActionDefinitionName()); + + for(ReplicationDefinition rd : replicationDefinitions) { + // Get the executing detail(s) for this definition + ExecutionDetails details = getExecutionDetails(rd, executing); + + // Set the core details + Map rdm = new HashMap(); + rdm.put(DEFINITION_NAME, rd.getReplicationName()); + rdm.put(DEFINITION_ENABLED, rd.isEnabled()); + rdm.put(DEFINITION_DETAILS_URL, buildDefinitionDetailsUrl(rd)); + + // Do the status + setStatus(rd, details, rdm); + + // Add to the list of finished models + models.add(rdm); + } + } + + // Finish up + Map model = new HashMap(); + model.put(MODEL_DATA_LIST, models); + return model; + } + + + protected String buildDefinitionDetailsUrl(ReplicationDefinition replicationDefinition) + { + return "/api/replication-definition/" + replicationDefinition.getReplicationName(); + } + + /** + * Figures out the status that's one of: + * New|Running|CancelRequested|Completed|Failed|Cancelled + * by merging data from the action tracking service. + * Will also set the start and end dates, from either the + * replication definition or action tracking data, depending + * on the status. + */ + protected void setStatus(ReplicationDefinition replicationDefinition, Map model) + { + // Grab the running instance(s) of the action + List executing = + actionTrackingService.getExecutingActions(replicationDefinition); + // Now get the details of that + ExecutionDetails details = getExecutionDetails(replicationDefinition, executing); + // Finally have the status set + setStatus(replicationDefinition, details, model); + } + /** + * Figures out the status that's one of: + * New|Running|CancelRequested|Completed|Failed|Cancelled + * by merging data from the action tracking service. + * Will also set the start and end dates, from either the + * replication definition or action tracking data, depending + * on the status. + */ + protected void setStatus(ReplicationDefinition replicationDefinition, + ExecutionDetails details, Map model) + { + // Is it currently running? + if(details == null) { + // It isn't running, we can use the persisted details + model.put(DEFINITION_STATUS, replicationDefinition.getExecutionStatus().toString()); + model.put(DEFINITION_STARTED_AT, replicationDefinition.getExecutionStartDate()); + model.put(DEFINITION_ENDED_AT, replicationDefinition.getExecutionEndDate()); + return; + } + + // Use the details of the running copy + if(details.isCancelRequested()) { + model.put(DEFINITION_STATUS, "CancelRequested"); + } else { + model.put(DEFINITION_STATUS, "Running"); + } + model.put(DEFINITION_STARTED_AT, details.getStartedAt()); + model.put(DEFINITION_ENDED_AT, null); + } + + /** + * For the given Replication Definition, and list of executing + * actions (which may or may not be only for this definition), + * return a single execution details. + * + * Returns null if no copies of the definition are executing. + * Returns a predictable instance if more than one copy is + * executing. + */ + private ExecutionDetails getExecutionDetails(ReplicationDefinition replicationDefinition, + List executing) + { + // Figure out which of the running actions are us + List ours = new ArrayList(); + for(ExecutionSummary es : executing) { + if(es.getActionType().equals(replicationDefinition.getActionDefinitionName()) && + es.getActionId().equals(replicationDefinition.getId())) { + ours.add(es); + } + } + + // Do we have anything running at the moment + if(ours.size() == 0) { + // Not executing at the moment + return null; + } + + // We have at least one copy running + ExecutionSummary es; + if(executing.size() == 1) { + // Only one copy, life is simple + es = ours.get(0); + } else { + // More than one copy runing, joy + // Go for the lowest execution instance id, so + // we're predictable + es = ours.get(0); + for(ExecutionSummary e : ours) { + if(e.getExecutionInstance() < es.getExecutionInstance()) { + es = e; + } + } + } + + // Grab the details + return actionTrackingService.getExecutionDetails(es); + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/replication/ReplicationRestApiTest.java b/source/java/org/alfresco/repo/web/scripts/replication/ReplicationRestApiTest.java new file mode 100644 index 0000000000..cff1ea6dba --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/replication/ReplicationRestApiTest.java @@ -0,0 +1,123 @@ +/* + * 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.replication; + +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.ActionTrackingService; +import org.alfresco.service.cmr.replication.ReplicationService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.GUID; +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.Response; + +/** + * Tests for the Replication Webscripts + * @author Nick Burch + */ +public class ReplicationRestApiTest extends BaseWebScriptTest +{ + private static final String URL_DEFINITION = "api/replication-definition/"; + private static final String URL_DEFINITIONS = "api/replication-definitions"; + private static final String URL_RUNNING_ACTION = "api/running-action/"; + + private static final String USER_NORMAL = "Normal" + GUID.generate(); + + private TestPersonManager personManager; + private ReplicationService replicationService; + private ActionTrackingService actionTrackingService; + + public void testReplicationDefinitionsGet() throws Exception + { + Response response; + + + // Not allowed if you're not an admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getGuestUserName()); + response = sendRequest(new GetRequest(URL_DEFINITIONS), Status.STATUS_UNAUTHORIZED); + assertEquals(Status.STATUS_UNAUTHORIZED, response.getStatus()); + + AuthenticationUtil.setFullyAuthenticatedUser(USER_NORMAL); + response = sendRequest(new GetRequest(URL_DEFINITIONS), Status.STATUS_UNAUTHORIZED); + assertEquals(Status.STATUS_UNAUTHORIZED, response.getStatus()); + + + // If no definitions exist, you don't get anything back + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + response = sendRequest(new GetRequest(URL_DEFINITIONS), 200); + assertEquals(Status.STATUS_OK, response.getStatus()); + + String jsonStr = response.getContentAsString(); + JSONObject json = new JSONObject(jsonStr); + JSONArray results = json.getJSONArray("data"); + assertNotNull(results); + assertTrue(results.length() == 0); + + + // Add a definition, it should show up + // TODO + + + // Change the status to running, and re-check + // TODO + + + // Add a 2nd and 3rd + // TODO + + + // Cancel one of these + // TODO + + } + + @Override + protected void setUp() throws Exception + { + super.setUp(); + ApplicationContext appContext = getServer().getApplicationContext(); + + replicationService = (ReplicationService)appContext.getBean("ReplicationService"); + actionTrackingService = (ActionTrackingService)appContext.getBean("actionTrackingService"); + + MutableAuthenticationService authenticationService = (MutableAuthenticationService)appContext.getBean("AuthenticationService"); + PersonService personService = (PersonService)appContext.getBean("PersonService"); + NodeService nodeService = (NodeService)appContext.getBean("NodeService"); + personManager = new TestPersonManager(authenticationService, personService, nodeService); + + personManager.createPerson(USER_NORMAL); + } + + /* (non-Javadoc) + * @see junit.framework.TestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + personManager.clearPeople(); + } +}