diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.get.desc.xml index 42d0e3ddc5..2ae3bda474 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.get.desc.xml @@ -1,9 +1,9 @@ Get Rule Get the rule identified by the specified rule node reference. - /api/rules/{store_type}/{store_id}/{id} - /api/node/{store_type}/{store_id}/{id}/rules/{id} - /api/path/{store_type}/{store_id}/{id}/rules/{id} + /api/rules/{store_type}/{store_id}/{rule_id} + /api/node/{store_type}/{store_id}/{id}/rules/{rule_id} + /api/path/{store_type}/{store_id}/{id}/rules/{rule_id} user required diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.desc.xml index 9634e3f60a..739883b48e 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.desc.xml @@ -1,9 +1,9 @@ Put Rule Update the rule identified by the specified rule node reference. - /api/rules/{store_type}/{store_id}/{id} - /api/node/{store_type}/{store_id}/{id}/rules/{id} - /api/path/{store_type}/{store_id}/{id}/rules/{id} + /api/rules/{store_type}/{store_id}/{rule_id} + /api/node/{store_type}/{store_id}/{id}/rules/{rule_id} + /api/path/{store_type}/{store_id}/{id}/rules/{rule_id} user required diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.json.ftl index ce2f3f4bdd..71a03617ae 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/rule/rule.put.json.ftl @@ -1,2 +1,2 @@ <#import "rule.lib.ftl" as ruleLib/> -<@ruleLib.ruleJSON rule=rule/> \ No newline at end of file +<@ruleLib.ruleJSON rule=rule owningNodeRef=owningNodeRef/> \ No newline at end of file diff --git a/source/java/org/alfresco/repo/web/scripts/rule/RuleDelete.java b/source/java/org/alfresco/repo/web/scripts/rule/RuleDelete.java index a177a94a85..8e54d320f4 100644 --- a/source/java/org/alfresco/repo/web/scripts/rule/RuleDelete.java +++ b/source/java/org/alfresco/repo/web/scripts/rule/RuleDelete.java @@ -37,6 +37,16 @@ import org.alfresco.web.scripts.WebScriptRequest; /** * Web Script to DELETE the rule identified by the given rule node id. * + * NOTE - + * that if a value is provided for the 'id' URL template variable {id}, + * i.e. either of the following URL patterns have been used + * /api/node/{store_type}/{store_id}/{id}/rules/{rule_id} or + * /api/path/{store_type}/{store_id}/{id}/rules/{rule_id} + * then the rule owning node ref supplied therein will be ignored, + * as these URL templates are just provided for convenience and the + * rule owning node ref is retrieved by using the rule's identifying node + * ref (supplied in {rule_id}) + * * @author glen johnson at alfresco dot com */ public class RuleDelete extends DeclarativeWebScript @@ -100,28 +110,25 @@ public class RuleDelete extends DeclarativeWebScript } String ruleNodeId = req.getServiceMatch().getTemplateVars().get(REQ_TEMPL_VAR_RULE_NODE_ID); - // Handle if 'ruleNodeId' URL template token not provided + // Handle if 'rule_id' URL template token not provided if ((ruleNodeId == null) || (ruleNodeId.length() == 0)) { throw new WebScriptException(Status.STATUS_BAD_REQUEST, "The 'rule_id' URL template token has not been provided in URL"); } - // NOTE - - // that if a value is provided for the 'id' URL template variable {id}, - // i.e. either of the following URL patterns have been used - // /api/node/{store_type}/{store_id}/{id}/rules/{rule_id} or - // /api/path/{store_type}/{store_id}/{id}/rules/{rule_id} - // then the rule owning node ref supplied therein will be ignored, - // as these URL templates are just provided for convenience and the - // rule owning node ref is retrieved by using the rule's identifying node - // ref (supplied in {rule_id}) - // create the rule's identifying node reference from the given // URL template tokens NodeRef ruleNodeRef = this.rulesHelper.getNodeRefFromWebScriptUrl(req, storeType, storeId, ruleNodeId); - // get the rule using it's unique identifying node reference + // if ruleNodeRef referred to by {store_type} {store_id} {rule_id} is 'null' then the rule identified by that + // given node id or node path no longer exists + if (ruleNodeRef == null) + { + throw new WebScriptException(Status.STATUS_NOT_FOUND, "Rule identified by rule node/path - 'store_type': " + + storeType + " 'store_id': " + storeId + " and 'rule_id': " + ruleNodeId + " could not be found"); + } + Rule rule = this.ruleService.getRule(ruleNodeRef); NodeRef ruleOwningNodeRef = this.ruleService.getOwningNodeRef(rule); diff --git a/source/java/org/alfresco/repo/web/scripts/rule/RuleGet.java b/source/java/org/alfresco/repo/web/scripts/rule/RuleGet.java index 9addb92a9c..8b58dbec77 100644 --- a/source/java/org/alfresco/repo/web/scripts/rule/RuleGet.java +++ b/source/java/org/alfresco/repo/web/scripts/rule/RuleGet.java @@ -38,6 +38,16 @@ import org.alfresco.web.scripts.WebScriptRequest; /** * Web Script to GET the rule identified by the given rule node reference. * + * NOTE - + * that if a value is provided for the 'id' URL template variable {id}, + * i.e. either of the following URL patterns have been used + * /api/node/{store_type}/{store_id}/{id}/rules/{rule_id} or + * /api/path/{store_type}/{store_id}/{id}/rules/{rule_id} + * then the rule owning node ref supplied therein will be ignored, + * as these URL templates are just provided for convenience and the + * rule owning node ref is retrieved by using the rule's identifying node + * ref (supplied in {rule_id}) + * * @author glen johnson at alfresco dot com */ public class RuleGet extends DeclarativeWebScript @@ -45,7 +55,7 @@ public class RuleGet extends DeclarativeWebScript // private constants private static final String REQ_TEMPL_VAR_STORE_TYPE = "store_type"; private static final String REQ_TEMPL_VAR_STORE_ID = "store_id"; - private static final String REQ_TEMPL_VAR_RULE_NODE_ID = "id"; + private static final String REQ_TEMPL_VAR_RULE_NODE_ID = "rule_id"; // model property keys private static final String MODEL_PROP_KEY_RULE = "rule"; @@ -119,12 +129,12 @@ public class RuleGet extends DeclarativeWebScript // URL template tokens NodeRef ruleNodeRef = this.rulesHelper.getNodeRefFromWebScriptUrl(req, storeType, storeId, ruleNodeId); - // if ruleNodeRef referred to by {store_type} {store_id} {id} is 'null' then the rule identified by that + // if ruleNodeRef referred to by {store_type} {store_id} {rule_id} is 'null' then the rule identified by that // given node id or node path no longer exists if (ruleNodeRef == null) { throw new WebScriptException(Status.STATUS_NOT_FOUND, "Rule identified by rule node/path - 'store_type': " - + storeType + " 'store_id': " + storeId + " and 'id': " + ruleNodeId + " could not be found"); + + storeType + " 'store_id': " + storeId + " and 'rule_id': " + ruleNodeId + " could not be found"); } // get rule identified by the given rule node reference @@ -135,7 +145,7 @@ public class RuleGet extends DeclarativeWebScript // add objects to model for the template to render model.put(MODEL_PROP_KEY_RULE, rule); - model.put(MODEL_PROP_KEY_OWNING_NODE_REF, ruleOwningNodeRef); + model.put(MODEL_PROP_KEY_OWNING_NODE_REF, ruleOwningNodeRef.toString()); return model; } diff --git a/source/java/org/alfresco/repo/web/scripts/rule/RulePut.java b/source/java/org/alfresco/repo/web/scripts/rule/RulePut.java index 62617302c6..b9ed81d24f 100644 --- a/source/java/org/alfresco/repo/web/scripts/rule/RulePut.java +++ b/source/java/org/alfresco/repo/web/scripts/rule/RulePut.java @@ -39,6 +39,16 @@ import org.json.JSONObject; /** * Web Script to PUT (update) the rule identified by the given rule node reference. * + * NOTE - + * that if a value is provided for the 'id' URL template variable {id}, + * i.e. either of the following URL patterns have been used + * /api/node/{store_type}/{store_id}/{id}/rules/{rule_id} or + * /api/path/{store_type}/{store_id}/{id}/rules/{rule_id} + * then the rule owning node ref supplied therein will be ignored, + * as these URL templates are just provided for convenience and the + * rule owning node ref is retrieved by using the rule's identifying node + * ref (supplied in {rule_id}) + * * @author glen johnson at alfresco dot com */ public class RulePut extends DeclarativeWebScript @@ -46,10 +56,11 @@ public class RulePut extends DeclarativeWebScript // private constants private static final String REQ_TEMPL_VAR_STORE_TYPE = "store_type"; private static final String REQ_TEMPL_VAR_STORE_ID = "store_id"; - private static final String REQ_TEMPL_VAR_RULE_NODE_ID = "id"; + private static final String REQ_TEMPL_VAR_RULE_NODE_ID = "rule_id"; // model property keys private static final String MODEL_PROP_KEY_RULE = "rule"; + private static final String MODEL_PROP_KEY_OWNING_NODE_REF = "owningNodeRef"; // properties for services private RuleService ruleService; @@ -120,6 +131,14 @@ public class RulePut extends DeclarativeWebScript // URL template tokens NodeRef ruleNodeRef = this.rulesHelper.getNodeRefFromWebScriptUrl(req, storeType, storeId, ruleNodeId); + // if ruleNodeRef referred to by {store_type} {store_id} {rule_id} is 'null' then the rule identified by that + // given node id or node path no longer exists + if (ruleNodeRef == null) + { + throw new WebScriptException(Status.STATUS_NOT_FOUND, "Rule identified by rule node/path - 'store_type': " + + storeType + " 'store_id': " + storeId + " and 'rule_id': " + ruleNodeId + " could not be found"); + } + // get the rule JSON object sent in the request content (when PUTting the Rule) Object contentObj = req.parseContent(); if (contentObj == null || !(contentObj instanceof JSONObject)) @@ -129,19 +148,20 @@ public class RulePut extends DeclarativeWebScript } JSONObject ruleJson = (JSONObject)contentObj; - // get the rule object (identified by the rule node reference) - // updated by the details in the rule JSON object + // update the rule object identified by the given rule node reference + // with the details in the rule JSON Rule rule = this.rulesHelper.getRuleFromJson(ruleJson, ruleNodeRef); // get owning node ref that rule was // previous applied to - NodeRef owningNodeRef = this.ruleService.getOwningNodeRef(rule); + NodeRef ruleOwningNodeRef = this.ruleService.getOwningNodeRef(rule); - // re-apply rule to actionable node - this.ruleService.saveRule(owningNodeRef, rule); + // re-apply rule to owning node + this.ruleService.saveRule(ruleOwningNodeRef, rule); // add objects to model for the template to render model.put(MODEL_PROP_KEY_RULE, rule); + model.put(MODEL_PROP_KEY_OWNING_NODE_REF, ruleOwningNodeRef.toString()); return model; } diff --git a/source/java/org/alfresco/repo/web/scripts/rule/RuleServiceTest.java b/source/java/org/alfresco/repo/web/scripts/rule/RuleServiceTest.java index 31f02148e8..c50e2d0fce 100644 --- a/source/java/org/alfresco/repo/web/scripts/rule/RuleServiceTest.java +++ b/source/java/org/alfresco/repo/web/scripts/rule/RuleServiceTest.java @@ -25,9 +25,11 @@ package org.alfresco.repo.web.scripts.rule; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.evaluator.CompareMimeTypeEvaluator; import org.alfresco.repo.action.evaluator.ComparePropertyValueEvaluator; import org.alfresco.repo.action.executer.CompositeActionExecuter; import org.alfresco.repo.action.executer.CopyActionExecuter; @@ -36,7 +38,6 @@ import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.web.scripts.BaseWebScriptTest; -import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.rule.RuleType; @@ -49,6 +50,7 @@ import org.alfresco.web.scripts.Status; import org.alfresco.web.scripts.TestWebScriptServer.DeleteRequest; import org.alfresco.web.scripts.TestWebScriptServer.GetRequest; import org.alfresco.web.scripts.TestWebScriptServer.PostRequest; +import org.alfresco.web.scripts.TestWebScriptServer.PutRequest; import org.alfresco.web.scripts.TestWebScriptServer.Response; import org.json.JSONArray; import org.json.JSONObject; @@ -65,16 +67,17 @@ public class RuleServiceTest extends BaseWebScriptTest private AuthenticationComponent authenticationComponent; private NodeService nodeService; private PersonService personService; - private ActionService actionService; private NodeRef owningNodeRef1; private NodeRef testDestFolder1; - + private NodeRef testDestFolder2; + // private constants private static final String RULES_USER = "Rules.User"; private static final String RULES_USER_PASSWORD = "password"; private static final String RULES_TEST_OWNING_FOLDER_1 = "rulesTestOwningFolder1"; private static final String RULES_TEST_DEST_FOLDER_1 = "rulesTestDestinationFolder1"; + private static final String RULES_TEST_DEST_FOLDER_2 = "rulesTestDestinationFolder2"; @Override protected void setUp() throws Exception @@ -88,7 +91,6 @@ public class RuleServiceTest extends BaseWebScriptTest "AuthenticationComponent"); this.nodeService = (NodeService) getServer().getApplicationContext().getBean("NodeService"); this.personService = (PersonService) getServer().getApplicationContext().getBean("PersonService"); - this.actionService = (ActionService) getServer().getApplicationContext().getBean("ActionService"); // // various setup operations which need to be run as system user @@ -117,6 +119,9 @@ public class RuleServiceTest extends BaseWebScriptTest this.testDestFolder1 = this.nodeService.createNode(userHomeRef, ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, RULES_TEST_DEST_FOLDER_1), ContentModel.TYPE_FOLDER).getChildRef(); + this.testDestFolder2 = this.nodeService.createNode(userHomeRef, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, RULES_TEST_DEST_FOLDER_2), + ContentModel.TYPE_FOLDER).getChildRef(); } @Override @@ -345,8 +350,14 @@ public class RuleServiceTest extends BaseWebScriptTest // Construct rule JSON object JSONObject rule = new JSONObject(); - rule.put("owningNodeRef", owningNodeRef); - rule.put("ruleNodeRef", ruleNodeRef); + if (owningNodeRef != null) + { + rule.put("owningNodeRef", owningNodeRef.toString()); + } + if (ruleNodeRef != null) + { + rule.put("ruleNodeRef", ruleNodeRef.toString()); + } rule.put("title", title); rule.put("description", description); rule.put("ruleTypes", new JSONArray(ruleTypes)); @@ -421,9 +432,9 @@ public class RuleServiceTest extends BaseWebScriptTest // create nested action parameters JSONObject actionCopyParamsJson = new JSONObject(); actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_QNAME, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy")); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS); + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy").toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1.toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS.toString()); // create nested actions JSONObject actionCopyJson = getActionJsonObject(null, CopyActionExecuter.NAME, "CopyTitle", "CopyDesc", false, @@ -485,9 +496,9 @@ public class RuleServiceTest extends BaseWebScriptTest // create nested action parameters JSONObject actionCopyParamsJson = new JSONObject(); actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_QNAME, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy")); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS); + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy").toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1.toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS.toString()); // create nested action JSONObject actionCopyJson = getActionJsonObject(null, CopyActionExecuter.NAME, "CopyTitle", "CopyDesc", false, @@ -527,6 +538,7 @@ public class RuleServiceTest extends BaseWebScriptTest sendRequest(new GetRequest(url), Status.STATUS_NOT_FOUND); } + @SuppressWarnings("unchecked") public void testGetRule() throws Exception { // @@ -538,7 +550,7 @@ public class RuleServiceTest extends BaseWebScriptTest JSONObject condCompMimeTypeParams = new JSONObject(); condCompMimeTypeParams.put(ComparePropertyValueEvaluator.PARAM_VALUE, "image/png"); - // create conditions + // create conditions JSON array JSONObject conditionCompMimeType = getConditionJsonObject(null, condCompMimeTypeParams, "compare-mime-type", false, null); JSONArray conditions = new JSONArray(); conditions.put(conditionCompMimeType); @@ -546,9 +558,9 @@ public class RuleServiceTest extends BaseWebScriptTest // create parameters for nested actions JSONObject actionCopyParamsJson = new JSONObject(); actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_QNAME, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy")); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS); + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy").toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1.toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS.toString()); // create nested actions JSONObject actionCopyJson = getActionJsonObject(null, CopyActionExecuter.NAME, "CopyTitle", "CopyDesc", false, @@ -556,15 +568,15 @@ public class RuleServiceTest extends BaseWebScriptTest JSONArray nestedActions = new JSONArray(); nestedActions.put(actionCopyJson); - // create rule's composite action - JSONObject compoActionJson = getActionJsonObject(null, CompositeActionExecuter.NAME, "Rule1Action", "Rule1ActionDesc", + // create rule's root action + JSONObject ruleActionJson = getActionJsonObject(null, CompositeActionExecuter.NAME, "Rule1Action", "Rule1ActionDesc", false, null, conditions, nestedActions, null, null); - // create rule to POST + // create rule JSON List ruleTypes = new ArrayList(); ruleTypes.add(RuleType.UPDATE); - JSONObject ruleJson = getRuleJsonObject(this.owningNodeRef1, null, "Rule1", "Rule1Desc", ruleTypes, - compoActionJson, false, false, false, null); + JSONObject ruleJson = getRuleJsonObject(null, null, "Rule1", "Rule1Desc", ruleTypes, + ruleActionJson, false, false, false, null); // POST rule JSON to rules collection resource JSONObject resultPostRule = postRules(this.owningNodeRef1, ruleJson); @@ -583,9 +595,106 @@ public class RuleServiceTest extends BaseWebScriptTest Response response = sendRequest(new GetRequest(getRuleRulesBasedUrl), Status.STATUS_OK); JSONObject resultGetRule = new JSONObject(response.getContentAsString()); - String resultRuleNodeRefStr = resultGetRule.getString("ruleNodeRef"); + // + // validate properties returned in GET Rule response + // - assertEquals(ruleNodeRefStr, resultRuleNodeRefStr); + // + // check rule properties + // + assertEquals(this.owningNodeRef1.toString(), resultGetRule.getString("owningNodeRef")); + assertEquals(ruleNodeRefStr, resultGetRule.getString("ruleNodeRef")); + assertEquals("Rule1", resultGetRule.getString("title")); + assertEquals("Rule1Desc", resultGetRule.getString("description")); + assertEquals(false, resultGetRule.getBoolean("executeAsync")); + assertEquals(false, resultGetRule.getBoolean("ruleDisabled")); + assertEquals(false, resultGetRule.getBoolean("appliedToChildren")); + + // + // check rule types + // + JSONArray resultRuleTypes = resultGetRule.getJSONArray("ruleTypes"); + assertEquals(1, resultRuleTypes.length()); + assertEquals(RuleType.UPDATE, resultRuleTypes.getString(0)); + + // retrieve ID for copy action + JSONObject resultRuleAction = resultGetRule.getJSONObject("action"); + JSONObject resultNestedActions = resultRuleAction.getJSONObject("actions"); + Iterator actionKeysIter = resultNestedActions.keys(); + String resultCopyActionID = null; + while (actionKeysIter.hasNext()) + { + String key = actionKeysIter.next(); + + JSONObject resultAction = resultNestedActions.getJSONObject(key); + String actionDefName = resultAction.getString("actionDefinitionName"); + + if (actionDefName.equals(CopyActionExecuter.NAME)) + { + resultCopyActionID = resultAction.getString("id"); + break; + } + } + + assertNotNull(resultCopyActionID); + + // retrieve copy action + JSONObject resCopyAction = resultNestedActions.getJSONObject(resultCopyActionID); + + // + // check copy action properties + // + assertEquals(CopyActionExecuter.NAME, resCopyAction.getString("actionDefinitionName")); + assertEquals("CopyTitle", resCopyAction.getString("title")); + assertEquals("CopyDesc" , resCopyAction.getString("description")); + assertEquals(false, resCopyAction.getBoolean("executeAsync")); + + // + // check copy action parameters + // + JSONObject resCopyActionParams = resCopyAction.getJSONObject("parameterValues"); + assertEquals(QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, "copy").toString(), + resCopyActionParams.getString(MoveActionExecuter.PARAM_ASSOC_QNAME)); + assertEquals(this.testDestFolder1.toString(), resCopyActionParams.getString(MoveActionExecuter.PARAM_DESTINATION_FOLDER)); + assertEquals(ContentModel.ASSOC_CONTAINS.toString(), resCopyActionParams.getString(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME)); + + // retrieve ID for compare MIME-type condition + JSONObject resultConditions = resultRuleAction.getJSONObject("conditions"); + Iterator condKeysIter = resultConditions.keys(); + String resultCondCompMimeTypeID = null; + while (condKeysIter.hasNext()) + { + String key = condKeysIter.next(); + + JSONObject resultCondition = resultConditions.getJSONObject(key); + String condDefName = resultCondition.getString("conditionDefinitionName"); + + if (condDefName.equals(CompareMimeTypeEvaluator.NAME)) + { + resultCondCompMimeTypeID = resultCondition.getString("id"); + break; + } + } + + assertNotNull(resultCondCompMimeTypeID); + + // + // retrieve compare MIME-type condition + // + JSONObject resCompMimeTypeCond = resultConditions.getJSONObject(resultCondCompMimeTypeID); + + // + // check compare MIME type condition's properties + // + assertEquals(CompareMimeTypeEvaluator.NAME, resCompMimeTypeCond.getString("conditionDefinitionName")); + assertEquals(false, resCompMimeTypeCond.getBoolean("invertCondition")); + + // + // retrieve compare MIME-type condition's parameter JSON and check its value + // + JSONObject resCondCmpMimeTypeParams = resCompMimeTypeCond.getJSONObject("parameterValues"); + assertEquals("image/png", resCondCmpMimeTypeParams.getString(ComparePropertyValueEvaluator.PARAM_VALUE)); } public void testGetRules() throws Exception @@ -606,9 +715,9 @@ public class RuleServiceTest extends BaseWebScriptTest // create parameters for nested actions JSONObject actionCopyParamsJson = new JSONObject(); actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_QNAME, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy")); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1); - actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS); + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy").toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1.toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS.toString()); // create nested actions JSONObject actionCopyJson = getActionJsonObject(null, CopyActionExecuter.NAME, "CopyTitle", "CopyDesc", false, @@ -650,8 +759,234 @@ public class RuleServiceTest extends BaseWebScriptTest assertTrue(postedRuleFound == true); } - public void testPostRules() - throws Exception + @SuppressWarnings("unchecked") + public void testPutRule() throws Exception + { + // ------------------------- + // Create a Rule - POST Rule + // ------------------------- + + // create condition parameters + JSONObject condCompMimeTypeParams = new JSONObject(); + condCompMimeTypeParams.put(ComparePropertyValueEvaluator.PARAM_VALUE, "image/png"); + + // + // create conditions JSON + // + JSONObject conditionCompMimeType = getConditionJsonObject(null, condCompMimeTypeParams, "compare-mime-type", false, null); + JSONArray conditions = new JSONArray(); + conditions.put(conditionCompMimeType); + + // + // create parameters for nested actions JSON + // + JSONObject actionCopyParamsJson = new JSONObject(); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_QNAME, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "copy").toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder1.toString()); + actionCopyParamsJson.put(MoveActionExecuter.PARAM_ASSOC_TYPE_QNAME, ContentModel.ASSOC_CONTAINS.toString()); + + // + // create nested actions JSON + // + JSONObject actionCopyJson = getActionJsonObject(null, CopyActionExecuter.NAME, "CopyTitle", "CopyDesc", false, + actionCopyParamsJson, null, null, null, null); + JSONArray nestedActions = new JSONArray(); + nestedActions.put(actionCopyJson); + + // + // create rule's composite action JSON + // + JSONObject compoActionJson = getActionJsonObject(null, CompositeActionExecuter.NAME, "Rule1Action", "Rule1ActionDesc", + false, null, conditions, nestedActions, null, null); + + // + // create rule to POST JSON + // + List ruleTypes = new ArrayList(); + ruleTypes.add(RuleType.UPDATE); + JSONObject ruleJson = getRuleJsonObject(this.owningNodeRef1, null, "Rule", "RuleDesc", ruleTypes, + compoActionJson, false, false, false, null); + + // + // POST rule JSON to rules collection resource JSON + // + JSONObject resultRuleJson = postRules(this.owningNodeRef1, ruleJson); + + // get the rule node ref from the rule details returned so that + // we can retrieve the rule later + String ruleNodeRefStr = resultRuleJson.getString("ruleNodeRef"); + NodeRef ruleNodeRef = new NodeRef(ruleNodeRefStr); + + // -------------------------- + // Update the Rule - PUT Rule + // -------------------------- + + // create parameter for compare MIME-type condition with updated parameter value + JSONObject putCondCompMimeTypeParams = new JSONObject(); + putCondCompMimeTypeParams.put(ComparePropertyValueEvaluator.PARAM_VALUE, "image/jpeg"); + + // retrieve ID for compare MIME-type condition + JSONObject resultRuleAction = resultRuleJson.getJSONObject("action"); + JSONObject resultConditions = resultRuleAction.getJSONObject("conditions"); + Iterator condKeysIter = resultConditions.keys(); + String resultCondCompMimeTypeID = null; + while (condKeysIter.hasNext()) + { + String key = condKeysIter.next(); + + JSONObject resultCondition = resultConditions.getJSONObject(key); + String condDefName = resultCondition.getString("conditionDefinitionName"); + + if (condDefName.equals("compare-mime-type")) + { + resultCondCompMimeTypeID = resultCondition.getString("id"); + break; + } + } + + assertNotNull(resultCondCompMimeTypeID); + + // create compare MIME-type condition with updated condition parameters and updated properties + JSONObject putConditionCompMimeType = new JSONObject(); + putConditionCompMimeType.put("id", resultCondCompMimeTypeID); + putConditionCompMimeType.put("parameterValues", putCondCompMimeTypeParams); + putConditionCompMimeType.put("conditionDefinitionName", "compare-mime-type"); + putConditionCompMimeType.put("invertCondition", true); + + // create conditions JSON array and add updated compare MIME-type condition + JSONArray putConditions = new JSONArray(); + putConditions.put(putConditionCompMimeType); + + // create parameters for copy action JSON with updated parameter values + JSONObject putCopyActionParams = new JSONObject(); + putCopyActionParams.put(MoveActionExecuter.PARAM_DESTINATION_FOLDER, this.testDestFolder2.toString()); + + // retrieve ID for copy action + JSONObject resultActions = resultRuleAction.getJSONObject("actions"); + Iterator actionKeysIter = resultActions.keys(); + String resultCopyActionID = null; + while (actionKeysIter.hasNext()) + { + String key = actionKeysIter.next(); + + JSONObject resultAction = resultActions.getJSONObject(key); + String actionDefName = resultAction.getString("actionDefinitionName"); + + if (actionDefName.equals(CopyActionExecuter.NAME)) + { + resultCopyActionID = resultAction.getString("id"); + break; + } + } + + assertNotNull(resultCopyActionID); + + // create copy action with updated action parameters and updated properties + JSONObject putCopyAction = new JSONObject(); + putCopyAction.put("id", resultCopyActionID); + putCopyAction.put("actionDefinitionName", CopyActionExecuter.NAME); + putCopyAction.put("title", "CopyTitleUpdated"); + putCopyAction.put("description", "CopyDescUpdated"); + putCopyAction.put("executeAsync", true); + putCopyAction.put("parameterValues", putCopyActionParams); + + // create nested actions JSON array and add updated copy action + JSONArray putNestedActions = new JSONArray(); + putNestedActions.put(putCopyAction); + + // create rule action JSON (root action) with updated properties, conditions and nested actions + JSONObject putRuleAction = new JSONObject(); + putRuleAction.put("id", resultRuleAction.getString("id")); + putRuleAction.put("actionDefinitionName", CompositeActionExecuter.NAME); + putRuleAction.put("actions", putNestedActions); + putRuleAction.put("conditions", putConditions); + + // create updated rule types JSON array + JSONArray putRuleTypes = new JSONArray(); + putRuleTypes.put(RuleType.INBOUND); + + // create rule JSON with updated composite action, updated rule types and updated rule properties + JSONObject putRuleJson = new JSONObject(); + putRuleJson.put("title", "RuleTitleUpdated"); + putRuleJson.put("description", "RuleDescUpdated"); + putRuleJson.put("ruleTypes", putRuleTypes); + putRuleJson.put("action", putRuleAction); + putRuleJson.put("executeAsync", true); + putRuleJson.put("ruleDisabled", true); + + // + // update rule resource with updated rule JSON with PUT Rule + // + + String url = "/api/rules/" + ruleNodeRef.getStoreRef().getProtocol() + "/" + + ruleNodeRef.getStoreRef().getIdentifier() + "/" + ruleNodeRef.getId(); + + sendRequest(new PutRequest(url, putRuleJson.toString(), "application/json"), + Status.STATUS_OK); + + // ----------------------------------------------- + // Get the updated Rule - GET Rule + // and validate that the response thereof contains + // the updated field values + // ----------------------------------------------- + + // + // get the updated rule + // + Response responseGetRule = sendRequest(new GetRequest(url), Status.STATUS_OK); + JSONObject resGetRule = new JSONObject(responseGetRule.getContentAsString()); + + // + // check updated rule fields + // + assertEquals("RuleTitleUpdated", resGetRule.getString("title")); + assertEquals("RuleDescUpdated", resGetRule.getString("description")); + assertEquals(true, resGetRule.getBoolean("executeAsync")); + assertEquals(true, resGetRule.getBoolean("ruleDisabled")); + + // + // retrieve updated copy action + // + JSONObject resRuleAction = resGetRule.getJSONObject("action"); + JSONObject resNestedActions = resRuleAction.getJSONObject("actions"); + String copyActionId = putCopyAction.getString("id"); + JSONObject resCopyAction = resNestedActions.getJSONObject(copyActionId); + + // + // check updated copy action fields + // + assertEquals(CopyActionExecuter.NAME, resCopyAction.getString("actionDefinitionName")); + assertEquals("CopyTitleUpdated", resCopyAction.getString("title")); + assertEquals("CopyDescUpdated" , resCopyAction.getString("description")); + assertEquals(true, resCopyAction.getBoolean("executeAsync")); + + // + // check updated copy action parameters + // + JSONObject resCopyActionParams = resCopyAction.getJSONObject("parameterValues"); + assertEquals(this.testDestFolder2.toString(), resCopyActionParams.getString(MoveActionExecuter.PARAM_DESTINATION_FOLDER)); + + // + // retrieve updated compare MIME-type condition + // + JSONObject resConditions = resRuleAction.getJSONObject("conditions"); + String compMimeTypeCondId = putConditionCompMimeType.getString("id"); + JSONObject resCompMimeTypeCond = resConditions.getJSONObject(compMimeTypeCondId); + + // + // check updated compare MIME type condition's fields + // + assertEquals(true, resCompMimeTypeCond.getBoolean("invertCondition")); + + // + // retrieve compare MIME-type condition's parameter JSON and check its value + // + JSONObject resCondCmpMimeTypeParams = resCompMimeTypeCond.getJSONObject("parameterValues"); + assertEquals("image/jpeg", resCondCmpMimeTypeParams.getString(ComparePropertyValueEvaluator.PARAM_VALUE)); + } + + public void testPostRules() throws Exception { // create condition parameters for compare MIME type condition JSONObject condCompMimeTypeParams = new JSONObject(); @@ -692,8 +1027,6 @@ public class RuleServiceTest extends BaseWebScriptTest // validate rule result // - NodeRef resultRuleNodeRef = new NodeRef(resultRule.getString("ruleNodeRef")); - assertEquals(this.owningNodeRef1.toString(), resultRule.getString("owningNodeRef")); assertEquals("Rule1", resultRule.getString("title")); assertEquals("Rule1Desc", resultRule.getString("description")); @@ -800,8 +1133,6 @@ public class RuleServiceTest extends BaseWebScriptTest JSONArray result = new JSONArray(response.getContentAsString()); assertTrue(result.length() > 0); - - System.out.println(result); } public void testGetConditionDef() throws Exception diff --git a/source/java/org/alfresco/repo/web/scripts/rule/RulesGet.java b/source/java/org/alfresco/repo/web/scripts/rule/RulesGet.java index fe33cadf1a..f347e12fe7 100644 --- a/source/java/org/alfresco/repo/web/scripts/rule/RulesGet.java +++ b/source/java/org/alfresco/repo/web/scripts/rule/RulesGet.java @@ -155,7 +155,7 @@ public class RulesGet extends DeclarativeWebScript // add objects to model for the template to render model.put(MODEL_PROP_KEY_RULES, rules); - model.put(MODEL_PROP_KEY_OWNING_NODE_REF, owningNodeRef); + model.put(MODEL_PROP_KEY_OWNING_NODE_REF, owningNodeRef.toString()); return model; } diff --git a/source/java/org/alfresco/repo/web/scripts/rule/RulesHelper.java b/source/java/org/alfresco/repo/web/scripts/rule/RulesHelper.java index ca05fcb0ec..4967cce28f 100644 --- a/source/java/org/alfresco/repo/web/scripts/rule/RulesHelper.java +++ b/source/java/org/alfresco/repo/web/scripts/rule/RulesHelper.java @@ -171,62 +171,83 @@ public class RulesHelper rule = new Rule(); } + // + // set rule properties + // + try { - // - // set rule properties - // - if ((ruleJson.isNull("title") == true) && (update == false)) { - // the "title" field is mandatory, it is missing in the rule details, - // and we are creating a new rule, so throw an exception + // the "title" field is missing in the rule details, + // and we are creating a new rule, + // but the "title" field is mandatory, so throw an exception throw new WebScriptException(Status.STATUS_BAD_REQUEST, "A new rule is being created but the 'title' " + "field, which is mandatory, has not been included in the rule details"); } - // otherwise go ahead and set the value if the field is present + // otherwise just go ahead and set the value if the field is present + // to over both creating and updating scenarios else if (ruleJson.isNull("title") == false) { String ruleTitle = ruleJson.getString("title"); rule.setTitle(ruleTitle); } - + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the 'title' property " + + "in the received Rule JSON. It may contain invalid characters", je); + } + + try + { if (ruleJson.isNull("description") == false) { rule.setDescription(ruleJson.getString("description")); } + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the 'description' property " + + "in the received Rule JSON. It may contain invalid characters. The title of the offending rule is '" + + ruleJson.optString("title") + "'", je); + } + + + // set values from the respective Boolean fields below, but if the + // if the value given for a field does not equate to either + // 'true' or 'false', then set it to a default value of false + + if (ruleJson.isNull("executeAsync") == false) + { + rule.setExecuteAsynchronously(ruleJson.optBoolean("executeAsync", false)); + } + + + if (ruleJson.isNull("ruleDisabled") == false) + { + rule.setRuleDisabled(ruleJson.optBoolean("ruleDisabled", false)); + } - // set values from the respective Boolean fields below, but if the - // if the value given for a field does not equate to either - // 'true' or 'false', then set it to a default value of false - - if (ruleJson.isNull("executeAsync") == false) - { - rule.setExecuteAsynchronously(ruleJson.optBoolean("executeAsync", false)); - } - - - if (ruleJson.isNull("ruleDisabled") == false) - { - rule.setRuleDisabled(ruleJson.optBoolean("ruleDisabled", false)); - } - - - if (ruleJson.isNull("appliedToChildren") == false) - { - rule.applyToChildren(ruleJson.optBoolean("appliedToChildren", false)); - } - - // - // set rule types present in the rule details onto the rule - // - + + if (ruleJson.isNull("appliedToChildren") == false) + { + rule.applyToChildren(ruleJson.optBoolean("appliedToChildren", false)); + } + + // + // set rule types present in the rule details onto the rule + // + + try + { if ((ruleJson.isNull("ruleTypes") == true) && (update == false)) { - // the "ruleTypes" field is mandatory, it is missing in the rule details, - // and we are creating a new rule so throw an exception + // the "ruleTypes" field is mandatory for rule creation. it is missing in the rule details + // from which we are creating a new rule, so throw an exception throw new WebScriptException(Status.STATUS_BAD_REQUEST, "A new rule is being created but the 'ruleTypes' " - + "field, which is mandatory, has not been included in the rule details"); + + "property, which is mandatory, has not been included in the rule details. The title of the " + + "offending rule is '" + ruleJson.optString("title") + "'"); } else if (ruleJson.isNull("ruleTypes") == false) { @@ -238,7 +259,8 @@ public class RulesHelper if (numRuleTypes < 1) { throw new WebScriptException(Status.STATUS_BAD_REQUEST, "At least one rule type needs to be present in " - + "in the rule details sent in the request content."); + + "in the rule details sent in the request content. The title of the " + + "offending rule is '" + ruleJson.optString("title") + "'"); } // add to the rule the rule type names sent in the rule details @@ -253,57 +275,83 @@ public class RulesHelper { throw new WebScriptException(Status.STATUS_BAD_REQUEST, "An invalid rule type name was given in the " + "rule details sent in the request content. Invalid rule type name given is: '" - + ruleTypeNameJson + "'"); + + ruleTypeNameJson + "'. The title of the offending rule containing the invalid rule type " + + "is '" + ruleJson.optString("title") + "'"); } } rule.setRuleTypes(ruleTypes); } - - if ((ruleJson.isNull("action") == true) && (update == false)) - { - // the "action" field is mandatory, it is missing in the rule details, - // and we are creating a new rule so throw an exception - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "A new rule is being created but the 'action' " - + "field, which is mandatory, has not been included in the rule details"); - } - else - { - // set the action supplied in the rule details onto the rule - JSONObject ruleActionJson = ruleJson.getJSONObject("action"); - - // if we're doing an update then the rule should already have - // this action set on it, so get the action object already on - // the rule - Action ruleActionToUpdate = null; - if (update == true) - { - String ruleActionJsonId = ruleActionJson.getString("id"); - ruleActionToUpdate = rule.getAction(); - - // throw a web script exception if the ID of the rule's action, - // already persisted to the repository, is not the same as the one - // given for the rule action's ID in the rule details that we - // wish to perform the rule update with - if (ruleActionToUpdate.getId().equals(ruleActionJsonId) == false) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "The ID sent in the rule details " - + "of the action directly associated with the rule we wish to update does not match " - + " the rule's action already persisted in the repository. The rule's nodeRef is: '" - + ruleNodeRefToUpdate + "', the action ID provided in the rule details is '" - + ruleActionJsonId + "', and the ID for the rule's action already persisted in the" - + " repository is '" + ruleActionToUpdate.getId() + "'"); - } - } - - - Action action = getActionFromJson(ruleActionJson, ruleActionToUpdate); - rule.setAction(action); - } } catch (JSONException je) { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, - "Problem creating rule from Rule Details sent in the request content.", je); + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the 'ruleTypes' " + + "property in the received Rule JSON. It may not be a JSON Array or one of the rule types " + + "therein may contain invalid characters. The title of the offending rule is '" + + ruleJson.optString("title") + "'", je); + } + + if ((ruleJson.isNull("action") == true) && (update == false)) + { + // the "action" field is mandatory, it is missing in the rule details, + // and we are creating a new rule so throw an exception + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "A new rule is being created but the 'action' " + + "field, which is mandatory, has not been included in the rule details. The title of the " + + "offending rule is '" + ruleJson.optString("title") + "'"); + } + else + { + // set the action supplied in the rule details onto the rule + JSONObject ruleActionJson; + try + { + ruleActionJson = ruleJson.getJSONObject("action"); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the 'action' " + + "property in the received rule JSON. It is mandatory and may be missing from the " + + "rule JSON. The title of the offending rule is '" + ruleJson.optString("title") + "'", je); + } + + // if we're doing an update then the rule should already have + // this action set on it, so get the action object already on + // the rule + Action ruleActionToUpdate = null; + if (update == true) + { + String ruleActionJsonId; + try + { + ruleActionJsonId = ruleActionJson.getString("id"); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the 'id' " + + "property in the rule's root action from the received rule JSON. It is mandatory when " + + "updating a rule and may be missing from the received rule JSON. The title of the " + + "offending rule is '" + ruleJson.optString("title") + "'", je); + } + + ruleActionToUpdate = rule.getAction(); + + // throw a web script exception if the ID of the rule's action, + // already persisted to the repository, is not the same as the one + // given for the rule action's ID in the rule details that we + // wish to perform the rule update with + if (ruleActionToUpdate.getId().equals(ruleActionJsonId) == false) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "The ID sent in the rule details " + + "of the action directly associated with the rule we wish to update does not match " + + " the rule's action already persisted in the repository. The rule's nodeRef is: '" + + ruleNodeRefToUpdate + "', the action ID provided in the rule details is '" + + ruleActionJsonId + "', and the ID for the rule's action already persisted in the" + + " repository is '" + ruleActionToUpdate.getId() + "'"); + } + } + + + Action action = getActionFromJson(ruleActionJson, ruleActionToUpdate); + rule.setAction(action); } return rule; @@ -316,7 +364,8 @@ public class RulesHelper * then this indicates that this action is to be updated from the action details * provided in the given JSON object and then returned. * If a 'null' is passed into this parameter, then this indicates that a new action is to - * be created from scratch and returned. + * be created from scratch, populated with the action details provided in the given JSON object + * and then returned. * * @param actionJson the action JSON object used to create/update the action with * @param actionToUpdate The action to be updated. @@ -324,7 +373,6 @@ public class RulesHelper * * @return The action created/updated from the given action JSON object */ - @SuppressWarnings("unchecked") public Action getActionFromJson(JSONObject actionJson, Action actionToUpdate) { ActionService actionService = this.serviceRegistry.getActionService(); @@ -336,156 +384,197 @@ public class RulesHelper // Action action = null; + boolean updateAction = false; + + // if an action has been given to update with fields present in the given action + // JSON object, then set that action to the current action, and set the updateAction + // indicator to true if (actionToUpdate != null) { action = actionToUpdate; + updateAction = true; } + // + // create an action object from an "action" JSON object + // + + String actionDefinitionName; + try + { + actionDefinitionName = actionJson.getString("actionDefinitionName"); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the " + + "'actionDefinitionName' property from the the received action JSON. It is mandatory " + + "and may be missing from the received action JSON. The 'title' of the offending action is '" + + actionJson.optString("title") + "'", je); + } + JSONArray nestedActionsJson = actionJson.optJSONArray("actions"); + + // if action's definition name denotes that it is a composite action and the + // action JSON object has nested actions, then treat it as a composite action + if ((actionDefinitionName.equals(COMPOSITE_ACTION_DEF_NAME)) == true && (nestedActionsJson != null)) + { + // if we are updating an action which is a composite action, + // but the action definition name in the action JSON is not 'composite-action' + // throw a web script exception + if ((updateAction == true) && ((action instanceof CompositeAction) == false)) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "The action you wish to update " + + "is not a composite action object, but the action definition name in the action JSON is " + + "'composite-action'. Thus this action could not be updated with the action JSON sent"); + } + + // TODO remove this look-up map when the back-end provides an easy way to lookup actions + // on a composite action by ID + + // if we are updating an existing composite action then create a map to easily look up the + // nested action objects for each nested action details provided in the action JSON object + Map nestedActionsMap = new HashMap(); + if ((updateAction == true) && ((action instanceof CompositeAction) == true)) + { + List nestedActions = ((CompositeAction)action).getActions(); + for (Action nestedAction: nestedActions) + { + nestedActionsMap.put(nestedAction.getId(), nestedAction); + } + } + // else if we are not updating then create composite action object + // for scratch + else if (updateAction == false) + { + action = actionService.createCompositeAction(); + } + + // recursively add nested actions to this composite action + // as some of those nested actions could also be composite actions + int numNestedActionsJson = nestedActionsJson.length(); + for (int i=0; i < numNestedActionsJson; i++) + { + JSONObject nestedActionJson = nestedActionsJson.optJSONObject(i); + + if (nestedActionJson != null) + { + Action nestedAction = null; + + // if we are doing an action update, then update the nested actions from + // the nested action JSON + if (updateAction == true) + { + String nestedActionJsonID; + try + { + nestedActionJsonID = nestedActionJson.getString("id"); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the " + + "'id' property from the received action JSON. It is mandatory when updating an action" + + "and may be missing from the received action JSON. The 'title' of the offending action is '" + + nestedActionJson.optString("title") + "'", je); + } + + // lookup to see if nested action from nested action JSON + // already exists on composite action, in which case, update + // the nested action with the nested action details provided in the + // nested action JSON + Action nestedActionToUpdate = nestedActionsMap.get(nestedActionJsonID); + if (nestedActionToUpdate != null) + { + // first remove the nested action to be updated from the parent + // composite action + ((CompositeAction)action).removeAction(nestedActionToUpdate); + + // update that nested action with the details in the nested action JSON + nestedAction = getActionFromJson(nestedActionJson, nestedActionToUpdate); + } + } + // else we are not doing an action update so just pass in the + // actionToUpdate as 'null' + else + { + nestedAction = getActionFromJson(nestedActionJson, null); + } + + // add the nested action (newly created or just updated) to + // the composite action + ((CompositeAction)action).addAction(nestedAction); + } + } + } + // else if the action definition name denotes that this is not a composite action, + // but nested actions have been provided in the action JSON Object + // then throw a Web Script Exception + else if ((actionDefinitionName.equals(COMPOSITE_ACTION_DEF_NAME) == false) && (nestedActionsJson != null)) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Nested actions were sent in the action details " + + "having Title: '" + actionJson.optString("title") + "', but the action definition " + + "name thereof is not '" + COMPOSITE_ACTION_DEF_NAME + "' as expected. Instead, the action's " + + "definition name is '" + actionDefinitionName + "'."); + } + + // if we are not updating an existing action and there are no nested actions in the action JSON, + // just create a new action from the action definition without any recursive nested action handling + if ((updateAction == false) && (nestedActionsJson == null)) + { + action = actionService.createAction(actionDefinitionName); + } + + // + // set action properties + // + try { - // - // create an action object from an "action" JSON object - // - - String actionDefinitionName = actionJson.getString("actionDefinitionName"); - JSONArray nestedActionsJson = actionJson.optJSONArray("actions"); - - // if action's definition name denotes that it is a composite action and the - // action JSON object has nested actions, then treat it as a composite action - if ((actionDefinitionName.equals(COMPOSITE_ACTION_DEF_NAME)) == true && (nestedActionsJson != null)) + if ((actionJson.isNull("title") == true) && (updateAction == false)) { - // if we are updating an existing action and the given - // actionToUpdate is not a composite action, then throw a - // web script exception - if ((actionToUpdate != null) && ((actionToUpdate instanceof CompositeAction) == false)) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "The action directly associated with the " - + "rule you wish to update is not a composite action. Thus this action could not be updated with " - + "the given action JSON - the action details sent"); - } - - // TODO remove this look-up map when the back-end provides an easy way to lookup actions - // on a composite action by ID - - // if we are updating an existing composite action then create a map to easily look up the - // nested action objects for each nested action details provided in the action JSON object - Map nestedActionsMap = new HashMap(); - if ((actionToUpdate != null) && ((actionToUpdate instanceof CompositeAction) == true)) - { - List nestedActions = ((CompositeAction)action).getActions(); - for (Action nestedAction: nestedActions) - { - nestedActionsMap.put(nestedAction.getId(), nestedAction); - } - } - // else if we are not updating then create composite action object - // for scratch - else if (actionToUpdate == null) - { - action = actionService.createCompositeAction(); - } - - // recursively add nested actions to this composite action - // as some of those nested actions could also be composite actions - int numNestedActionsJson = nestedActionsJson.length(); - for (int i=0; i < numNestedActionsJson; i++) - { - JSONObject nestedActionJson = nestedActionsJson.optJSONObject(i); - - if (nestedActionJson != null) - { - Action nestedAction = null; - - // if we are doing an action update, then update the nested actions from - // the nested action JSON - if (actionToUpdate != null) - { - String nestedActionJsonID = nestedActionJson.getString("id"); - - // lookup to see if nested action from nested action JSON - // already exists on composite action, in which case, update - // the nested action with the nested action details provided in the - // nested action JSON - Action nestedActionToUpdate = nestedActionsMap.get(nestedActionJsonID); - if (nestedActionToUpdate != null) - { - // remove the existing nested action to then be updated below with the - // updated one - - nestedAction = getActionFromJson(nestedActionJson, nestedActionToUpdate); - } - } - // else we are not doing an action update so just pass in the - // actionToUpdate as 'null' - else - { - nestedAction = getActionFromJson(nestedActionJson, null); - } - - ((CompositeAction)action).addAction(nestedAction); - } - } - } - // else if the action definition name denotes that this is not a composite action, - // but nested actions have been provided in the action JSON Object - // then throw a Web Script Exception - else if ((actionDefinitionName.equals(COMPOSITE_ACTION_DEF_NAME) == false) && (nestedActionsJson != null)) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Nested actions were sent in the action details " - + "having Title: '" + actionJson.optString("title") + "', but the action definition " - + "name thereof is not '" + COMPOSITE_ACTION_DEF_NAME + "' as expected. Instead, the action's " - + "definition name is '" + actionDefinitionName + "'."); - } - // else the action's definition name is 'composite-action' but no nested actions were provided in the action JSON - // (in which case we will just treat the action as a non-composite action anyway), otherwise the action is not - // defined as a composite action, and no nested actions were sent in the action JSON, so just create it as a - // non-composite action - else - { - action = actionService.createAction(actionDefinitionName); - } - - // - // set action properties - // - - - if ((actionJson.isNull("title") == true) && (actionToUpdate == null)) - { - // the "title" field is mandatory, it is missing in the rule details, - // and we are creating a new rule, so throw an exception + // the "title" field is missing in the action details, + // and we are creating a new rule, + // but the "title" field is mandatory, so throw an exception throw new WebScriptException(Status.STATUS_BAD_REQUEST, "A new rule is being created but the 'title' " + "field, which is mandatory, has not been included in the rule details"); } // otherwise go ahead and set the value if the field is present + // to cover both create and update scenarios else if (actionJson.isNull("title") == false) { action.setTitle(actionJson.getString("title")); } - - if (actionJson.isNull("description") == false) - { - action.setDescription(actionJson.optString("description")); - } - - // If a value has been provided in the action details for the "executeAsync" field - // then set it to the action object. - // If the value given for a field does not equate to either - // 'true' or 'false', then set it to a default value of false - - if (actionJson.isNull("executeAsync") == false) - { - action.setExecuteAsynchronously(actionJson.optBoolean("executeAsync", false)); - } - - // set compensating action on current action if a compensating action is present - // in the action JSON Object + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the " + + "'title' property from the received action JSON. It is mandatory and may be " + + "missing from the received action JSON, or else it may contain invalid characters.", je); + } + + if (actionJson.isNull("description") == false) + { + action.setDescription(actionJson.optString("description")); + } + + // If a value has been provided in the action details for the "executeAsync" field + // then set it to the action object. + // If the value given for a field does not equate to either + // 'true' or 'false', then set it to a default value of false + + if (actionJson.isNull("executeAsync") == false) + { + action.setExecuteAsynchronously(actionJson.optBoolean("executeAsync", false)); + } + + // set compensating action on current action if a compensating action is present + // in the action JSON Object + try + { if (actionJson.isNull("compensatingAction") == false) { JSONObject compActionJson = actionJson.getJSONObject("compensatingAction"); Action compActionToUpdate = null; - if (actionToUpdate != null) + if (updateAction) { compActionToUpdate = action.getCompensatingAction(); } @@ -493,120 +582,186 @@ public class RulesHelper action.setCompensatingAction(compAction); } - - // get the action's definition - ParameterizedItemDefinition actionDef = actionService.getActionDefinition(actionDefinitionName); - - // if there are parameter values in the action JSON object then - // set them onto the action object - // - JSONObject actionParamValuesJson = actionJson.optJSONObject("parameterValues"); - if (actionParamValuesJson != null) - { - setParameterValuesOnParameterizedItemFromJson(action, actionDef, actionParamValuesJson); - } - - // - // set conditions on the current action - // - - if (actionJson.isNull("conditions") == false) - { - JSONArray conditionsJson = actionJson.getJSONArray("conditions"); - - // if we are doing an update then build up a condition map - // do be able to do a condition look-up by ID for each condition included - // in the condition JSON - the condition details - - Map conditionsMap = new HashMap(); - if (actionToUpdate != null) - { - List actionConditions = actionToUpdate.getActionConditions(); - for (ActionCondition actionCondition : actionConditions) - { - conditionsMap.put(actionCondition.getId(), actionCondition); - } - } - - // get each condition and add it to the action - - int numConditionsJson = conditionsJson.length(); - for (int conditionJsonIndex = 0; conditionJsonIndex < numConditionsJson; conditionJsonIndex++) - { - ActionCondition condition = null; - JSONObject conditionJson = conditionsJson.getJSONObject(conditionJsonIndex); - String conditionDefName = null; - - // if we are doing an update, then get the existing condition matching - // the condition ID given in the condition JSON, and update that with - // the condition fields given therein - if (actionToUpdate != null) - { - String conditionJsonId = conditionJson.getString("id"); - condition = conditionsMap.get(conditionJsonId); - conditionDefName = condition.getActionConditionDefinitionName(); - } - // we are not doing an update, so create the condition using the given condition - // definition name and then populate this new condition from the fields given in - // the condition JSON - else - { - // we are not doing an update, so if the conditionDefinitionName has not been provided - // in the condition JSON then throw a web script exception - if (conditionJson.isNull("conditionDefinitionName")) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "A condition details at index: '" - + conditionJsonIndex + "' for action ID '" + action.getId() + " could not be created " - + "because the 'conditionDefinitionName' field is missing from the condition details sent"); - } - // else condition def name has been provided in the condition details - else - { - conditionDefName = conditionJson.getString("conditionDefinitionName"); - } - - condition = actionService.createActionCondition(conditionDefName); - } - - // get the condition definition object - ParameterizedItemDefinition conditionDef = actionService.getActionConditionDefinition(conditionDefName); - - // - // set the condition's properties - // - - // Set the value for the 'invertCondition' field if that field is sent - // in the condition JSON. If the value is sent, but it does not equate to - // either 'true' or 'false', then set it to a default value of false - if (conditionJson.isNull("invertCondition") == false) - { - condition.setInvertCondition(conditionJson.getBoolean("invertCondition")); - } - - // - // if there are parameter values on the condition JSON object - // then apply them to the condition object - // - JSONObject condParamValuesJson = conditionJson.optJSONObject("parameterValues"); - if (condParamValuesJson != null) - { - setParameterValuesOnParameterizedItemFromJson(condition, conditionDef, condParamValuesJson); - } - - // add condition to action object - action.addActionCondition(condition); - - // increment the condition JSON index - conditionJsonIndex++; - } - } } catch (JSONException je) { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the " + + "'compensatingAction' property from the received action JSON. The value of the property " + + "may not be a JSON Object or something may be wrong with one of the properties in the JSON " + + "Object for the compensating action itself. The 'title' of the offending action is '" + + actionJson.optString("title") + "'", je); + } + + // get the action's definition + ParameterizedItemDefinition actionDef = actionService.getActionDefinition(actionDefinitionName); + + // if there are parameter values in the action JSON object then + // set them onto the action object + // + JSONObject actionParamValuesJson = actionJson.optJSONObject("parameterValues"); + if (actionParamValuesJson != null) + { + setParameterValuesOnParameterizedItemFromJson(action, actionDef, actionParamValuesJson); + } + + // + // set conditions on the current action + // + + // if action JSON "conditions" property has a value + // then populate the current action's conditions with the + // conditions' details from within the action JSON + if (actionJson.isNull("conditions") == false) + { + JSONArray conditionsJson = actionJson.optJSONArray("conditions"); + // if conditionsJson is 'null' then throw a web script exception, because this means that + // the conditions property does not contain a JSON Array. The conditions property of an incoming + // action JSON object should be a JSON Array + if (conditionsJson == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "The 'conditions' property of the incoming " + + "action JSON titled '" + actionJson.optString("title") + "' is not a JSON Array"); + } - throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, - "Problem creating rule from JSON passed into Web Script.", je); + // If we are updating an existing action, then build up a map + // (keyed by condition ID) of conditions associated with that existing action. + // So for each condition JSON object (inside the incoming action JSON object), + // if the condition ID thereof is found in the map, then update the existing condition + // with the details sent in the JSON + + Map conditionsMap = new HashMap(); + if (updateAction == true) + { + List actionConditions = action.getActionConditions(); + for (ActionCondition actionCondition : actionConditions) + { + conditionsMap.put(actionCondition.getId(), actionCondition); + } + } + + // + // for each condition JSON object (associated with the incoming action JSON object): + // - if we are doing an update then update the corresponding condition object (if one exists) + // with the fields provided in the condition JSON object + // - if we are doing a create then create a new condition from the details provided + // in the condition JSON + + int numConditionsJson = conditionsJson.length(); + for (int conditionJsonIndex = 0; conditionJsonIndex < numConditionsJson; conditionJsonIndex++) + { + ActionCondition condition = null; + + JSONObject conditionJson; + try + { + conditionJson = conditionsJson.getJSONObject(conditionJsonIndex); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the " + + "condition held at index " + conditionJsonIndex + " in the JSON Array held in the " + + "'conditions' property from the received action JSON. The value held at that " + + "index may not be a JSON Object. The 'title' of the action with the offending " + + "'conditions' property is '" + actionJson.optString("title") + "'", je); + } + + String conditionDefName = null; + + // if we are doing an update, the current condition JSON has an ID, + // and a condition already exists matching that ID, then set the current + // condition to that existing condition and retrieve its definition name + if ((updateAction == true) && (conditionJson.isNull("id") == false)) + { + String conditionJsonId; + try + { + conditionJsonId = conditionJson.getString("id"); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the " + + "'id' property from a condition in the received action JSON. When doing an update " + + "this condition property is mandatory and may be missing from this condition in the " + + "action JSON sent or else the property may contain invalid parameters. " + + "The 'conditionDefinitionName' of the offending condition is '" + + conditionJson.optString("conditionDefinitionName") + "'", je); + } + + // set the current condition being processed to the existing conditions + condition = conditionsMap.get(conditionJsonId); + + // get the condition definition name + if (condition != null) + { + conditionDefName = condition.getActionConditionDefinitionName(); + } + + // remove the condition to be updated from its parent action + // The updated condition is added to the action at the end of the + // loop + action.removeActionCondition(condition); + } + + // if the current condition is still 'null' (we are not updating an existing condition), + // and if the conditionDefinitionName has not been provided in the condition JSON, then + // throw a web script exception because we need to create a new condition using the condition + // definition name provided in the condition JSON object + if ((condition == null) && (conditionJson.isNull("conditionDefinitionName"))) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "A condition details at index: '" + + conditionJsonIndex + "' for action ID '" + action.getId() + " could not be created " + + "because the 'conditionDefinitionName' field is missing from the condition details sent"); + } + + // else condition def name has been provided in the condition details + if (condition == null) + { + try + { + conditionDefName = conditionJson.getString("conditionDefinitionName"); + } + catch (JSONException je) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "There was a problem reading the " + + "'conditionDefinitionName' property from a condition in the received action JSON. " + + "This condition property is mandatory and may be missing from this condition in the " + + "action JSON sent. The 'conditionDefinitionName' of the offending condition is '" + + conditionJson.optString("conditionDefinitionName") + "'", je); + } + + condition = actionService.createActionCondition(conditionDefName); + } + + // get the condition definition object + ParameterizedItemDefinition conditionDef = actionService.getActionConditionDefinition(conditionDefName); + + // + // populate the condition (newly created, or one being updated) + // with fields given in the condition JSONs the condition's properties + // + + // Set the value for the 'invertCondition' field if that field is sent + // in the condition JSON. If the value is sent, but it does not equate to + // either 'true' or 'false', then set it to a default value of false + if (conditionJson.isNull("invertCondition") == false) + { + condition.setInvertCondition(conditionJson.optBoolean("invertCondition")); + } + + // + // if there are parameter values on the condition JSON object + // then apply them to the condition object + // + JSONObject condParamValuesJson = conditionJson.optJSONObject("parameterValues"); + if (condParamValuesJson != null) + { + setParameterValuesOnParameterizedItemFromJson(condition, conditionDef, condParamValuesJson); + } + + // add condition to action object + action.addActionCondition(condition); + } } return action; diff --git a/source/java/org/alfresco/repo/web/scripts/rule/RulesPost.java b/source/java/org/alfresco/repo/web/scripts/rule/RulesPost.java index 8a9a6c5b46..f2b243dac4 100644 --- a/source/java/org/alfresco/repo/web/scripts/rule/RulesPost.java +++ b/source/java/org/alfresco/repo/web/scripts/rule/RulesPost.java @@ -133,7 +133,7 @@ public class RulesPost extends DeclarativeWebScript // URL template tokens NodeRef owningNodeRef = this.rulesHelper.getNodeRefFromWebScriptUrl(req, storeType, storeId, id); - // apply rule to actionable node + // apply rule to rule owning node this.ruleService.saveRule(owningNodeRef, rule); // add objects to model for the template to render