001/* 002 * Copyright 2023 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.ametys.plugins.workflow.dao; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Map.Entry; 024import java.util.Optional; 025import java.util.Set; 026import java.util.stream.Collectors; 027 028import org.apache.avalon.framework.component.Component; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.cocoon.ProcessingException; 033import org.apache.commons.lang3.StringUtils; 034import org.apache.commons.lang3.tuple.Pair; 035 036import org.ametys.core.ui.Callable; 037import org.ametys.plugins.workflow.EnhancedCondition; 038import org.ametys.plugins.workflow.EnhancedConditionExtensionPoint; 039import org.ametys.plugins.workflow.ModelItemTypeExtensionPoint; 040import org.ametys.plugins.workflow.component.WorkflowArgument; 041import org.ametys.plugins.workflow.support.AvalonTypeResolver; 042import org.ametys.plugins.workflow.support.WorflowRightHelper; 043import org.ametys.plugins.workflow.support.WorkflowElementDefinitionHelper; 044import org.ametys.plugins.workflow.support.WorkflowHelper; 045import org.ametys.plugins.workflow.support.WorkflowHelper.WorkflowVisibility; 046import org.ametys.plugins.workflow.support.WorkflowSessionHelper; 047import org.ametys.runtime.i18n.I18nizableText; 048import org.ametys.runtime.model.DefinitionContext; 049import org.ametys.runtime.model.ElementDefinition; 050import org.ametys.runtime.model.SimpleViewItemGroup; 051import org.ametys.runtime.model.StaticEnumerator; 052import org.ametys.runtime.model.View; 053import org.ametys.runtime.model.ViewElement; 054import org.ametys.runtime.model.disableconditions.DisableCondition; 055import org.ametys.runtime.model.disableconditions.DisableCondition.OPERATOR; 056import org.ametys.runtime.model.disableconditions.DisableConditions; 057import org.ametys.runtime.plugin.component.AbstractLogEnabled; 058 059import com.opensymphony.workflow.Condition; 060import com.opensymphony.workflow.TypeResolver; 061import com.opensymphony.workflow.WorkflowException; 062import com.opensymphony.workflow.loader.AbstractDescriptor; 063import com.opensymphony.workflow.loader.ActionDescriptor; 064import com.opensymphony.workflow.loader.ConditionDescriptor; 065import com.opensymphony.workflow.loader.ConditionalResultDescriptor; 066import com.opensymphony.workflow.loader.ConditionsDescriptor; 067import com.opensymphony.workflow.loader.DescriptorFactory; 068import com.opensymphony.workflow.loader.RestrictionDescriptor; 069import com.opensymphony.workflow.loader.WorkflowDescriptor; 070 071/** 072 * DAO for workflow conditions 073 */ 074public class WorkflowConditionDAO extends AbstractLogEnabled implements Component, Serviceable 075{ 076 /** The component role */ 077 public static final String ROLE = WorkflowConditionDAO.class.getName(); 078 079 /** The "and" type of condition */ 080 public static final String AND = "AND"; 081 082 /** The "or" type of condition */ 083 public static final String OR = "OR"; 084 085 /** Key for "and" label in tree */ 086 protected static final String __ANDI18N = "PLUGIN_WORKFLOW_TRANSITION_CONDITIONS_TYPE_AND"; 087 088 /** Key for "or" label in tree */ 089 protected static final String __ORI18N = "PLUGIN_WORKFLOW_TRANSITION_CONDITIONS_TYPE_OR"; 090 091 /** Extension point for workflow arguments data type */ 092 protected static ModelItemTypeExtensionPoint _workflowArgumentDataTypeExtensionPoint; 093 094 private static final String __OR_ROOT_OPERATOR = "or0"; 095 private static final String __AND_ROOT_OPERATOR = "and0"; 096 private static final String __STEP_RESULT_PREFIX = "step"; 097 private static final String __ROOT_RESULT_ID = "root"; 098 private static final String __ATTRIBUTE_CONDITIONS_LIST = "conditions-list"; 099 100 /** The workflow session helper */ 101 protected WorkflowSessionHelper _workflowSessionHelper; 102 103 /** The workflow helper */ 104 protected WorkflowHelper _workflowHelper; 105 106 /** The workflow right helper */ 107 protected WorflowRightHelper _workflowRightHelper; 108 109 /** The workflow result helper */ 110 protected WorkflowResultDAO _workflowResultDAO; 111 112 /** The workflow step DAO */ 113 protected WorkflowStepDAO _workflowStepDAO; 114 115 /** The workflow transition DAO */ 116 protected WorkflowTransitionDAO _workflowTransitionDAO; 117 118 /** Extension point for Conditions */ 119 protected EnhancedConditionExtensionPoint _enhancedConditionExtensionPoint; 120 121 /** The service manager */ 122 protected ServiceManager _manager; 123 124 public void service(ServiceManager smanager) throws ServiceException 125 { 126 _workflowHelper = (WorkflowHelper) smanager.lookup(WorkflowHelper.ROLE); 127 _workflowRightHelper = (WorflowRightHelper) smanager.lookup(WorflowRightHelper.ROLE); 128 _workflowSessionHelper = (WorkflowSessionHelper) smanager.lookup(WorkflowSessionHelper.ROLE); 129 _workflowResultDAO = (WorkflowResultDAO) smanager.lookup(WorkflowResultDAO.ROLE); 130 _workflowStepDAO = (WorkflowStepDAO) smanager.lookup(WorkflowStepDAO.ROLE); 131 _workflowTransitionDAO = (WorkflowTransitionDAO) smanager.lookup(WorkflowTransitionDAO.ROLE); 132 _workflowArgumentDataTypeExtensionPoint = (ModelItemTypeExtensionPoint) smanager.lookup(ModelItemTypeExtensionPoint.ROLE_WORKFLOW); 133 _enhancedConditionExtensionPoint = (EnhancedConditionExtensionPoint) smanager.lookup(EnhancedConditionExtensionPoint.ROLE); 134 _manager = smanager; 135 } 136 137 /** 138 * Get the condition's model items as fields to configure edition form panel 139 * @return the parameters field as Json readable map 140 * @throws ProcessingException exception while saxing view to json 141 */ 142 @Callable(rights = {"Workflow_Right_Edit", "Workflow_Right_Edit_User"}) 143 public Map<String, Object> getConditionsModel() throws ProcessingException 144 { 145 Map<String, Object> response = new HashMap<>(); 146 147 View view = new View(); 148 SimpleViewItemGroup fieldset = new SimpleViewItemGroup(); 149 fieldset.setName("conditions"); 150 151 Set<Pair<String, EnhancedCondition>> conditions = _enhancedConditionExtensionPoint.getAllConditions() 152 .stream() 153 .filter(this::_hasConditionRight) 154 .collect(Collectors.toSet()); 155 ElementDefinition<String> conditionsList = _getConditionsListModelItem(conditions); 156 List<WorkflowArgument> argumentModelItems = _getArgumentModelItems(conditions); 157 158 ViewElement conditionListView = new ViewElement(); 159 conditionListView.setDefinition(conditionsList); 160 fieldset.addViewItem(conditionListView); 161 162 for (WorkflowArgument functionArgument : argumentModelItems) 163 { 164 ViewElement argumentView = new ViewElement(); 165 argumentView.setDefinition(functionArgument); 166 fieldset.addViewItem(argumentView); 167 } 168 169 view.addViewItem(fieldset); 170 171 response.put("parameters", view.toJSON(DefinitionContext.newInstance().withEdition(true))); 172 173 return response; 174 } 175 176 private boolean _hasConditionRight(Pair<String, EnhancedCondition> condition) 177 { 178 List<WorkflowVisibility> conditionVisibilities = condition.getRight().getVisibilities(); 179 if (conditionVisibilities.contains(WorkflowVisibility.USER)) 180 { 181 return _workflowRightHelper.hasEditUserRight(); 182 } 183 else if (conditionVisibilities.contains(WorkflowVisibility.SYSTEM)) 184 { 185 return _workflowRightHelper.hasEditSystemRight(); 186 } 187 return false; 188 } 189 190 191 /** 192 * Get the model item for the list of conditions 193 * @param conditions a list of all the conditions with their ids 194 * @return an enum of the conditions as a model item 195 */ 196 private ElementDefinition<String> _getConditionsListModelItem(Set<Pair<String, EnhancedCondition>> conditions) 197 { 198 ElementDefinition<String> conditionsList = WorkflowElementDefinitionHelper.getElementDefinition( 199 __ATTRIBUTE_CONDITIONS_LIST, 200 new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_CONDITION_DIALOG_ROLE"), 201 new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_CONDITION_DIALOG_ROLE_DESC"), 202 true, 203 false 204 ); 205 206 StaticEnumerator<String> conditionStaticEnumerator = new StaticEnumerator<>(); 207 for (Pair<String, EnhancedCondition> condition : conditions) 208 { 209 conditionStaticEnumerator.add(condition.getRight().getLabel(), condition.getLeft()); 210 } 211 conditionsList.setEnumerator(conditionStaticEnumerator); 212 213 return conditionsList; 214 } 215 216 /** 217 * Get a list of workflow arguments model items with disable conditions on non related function selected 218 * @param conditions a list of all the conditions with their ids 219 * @return the list of model items 220 */ 221 protected List<WorkflowArgument> _getArgumentModelItems(Set<Pair<String, EnhancedCondition>> conditions) 222 { 223 List<WorkflowArgument> argumentModelItems = new ArrayList<>(); 224 for (Pair<String, EnhancedCondition> condition : conditions) 225 { 226 String conditionId = condition.getLeft(); 227 DisableConditions disableConditions = new DisableConditions(); 228 DisableCondition disableCondition = new DisableCondition(__ATTRIBUTE_CONDITIONS_LIST, OPERATOR.NEQ, conditionId); 229 disableConditions.getConditions().add(disableCondition); 230 231 for (WorkflowArgument arg : condition.getRight().getArguments()) 232 { 233 arg.setName(conditionId + "-" + arg.getName()); 234 arg.setDisableConditions(disableConditions); 235 argumentModelItems.add(arg); 236 } 237 } 238 return argumentModelItems; 239 } 240 241 /** 242 * Get current argument's values for condition 243 * @param workflowName unique name of current workflow 244 * @param actionId id of current action 245 * @param conditionNodeId id of selected node 246 * @return a map of the condition's arguments and their current values 247 * @throws ProcessingException exception while resolving condition 248 */ 249 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 250 public Map<String, Object> getConditionParametersValues(String workflowName, Integer actionId, String conditionNodeId) throws ProcessingException 251 { 252 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 253 _workflowRightHelper.checkEditRight(workflowDescriptor); 254 255 ConditionDescriptor condition = _getCondition(workflowDescriptor, actionId, conditionNodeId); 256 Map<String, String> args = new HashMap<>(); 257 Map<String, String> conditionCurrentArguments = condition.getArgs(); 258 String conditionId = conditionCurrentArguments.get("id"); 259 for (Entry<String, String> entry : conditionCurrentArguments.entrySet()) 260 { 261 String argName = entry.getKey().equals("id") ? __ATTRIBUTE_CONDITIONS_LIST : conditionId + "-" + entry.getKey(); 262 args.put(argName, entry.getValue()); 263 } 264 args.putAll(conditionCurrentArguments); 265 266 Map<String, Object> results = new HashMap<>(); 267 results.put("parametersValues", args); 268 return results; 269 } 270 271 /** 272 * Get the current condition as condition descriptor 273 * @param workflowDescriptor current workflow 274 * @param actionId id of current action 275 * @param conditionNodeId id of current node 276 * @return the conditions 277 */ 278 protected ConditionDescriptor _getCondition(WorkflowDescriptor workflowDescriptor, Integer actionId, String conditionNodeId) 279 { 280 ActionDescriptor action = workflowDescriptor.getAction(actionId); 281 282 String[] path = _workflowResultDAO.getPath(conditionNodeId); 283 int nodeToEdit = Integer.valueOf(path[path.length - 1].substring("condition".length())); 284 boolean isResult = path[0].startsWith(__STEP_RESULT_PREFIX); 285 286 ConditionsDescriptor rootOperator = isResult 287 ? (ConditionsDescriptor) _workflowResultDAO.getRootResultConditions(action.getConditionalResults(), _getStepId(conditionNodeId)).get(0) 288 : action.getRestriction().getConditionsDescriptor(); 289 String parentNodeId = conditionNodeId.substring(0, conditionNodeId.lastIndexOf('-')); 290 ConditionsDescriptor parentNode = _getConditionsNode(rootOperator, parentNodeId); 291 ConditionDescriptor condition = (ConditionDescriptor) parentNode.getConditions().get(nodeToEdit); 292 293 return condition; 294 } 295 296 /** 297 * Edit the condition 298 * @param workflowName unique name of current workflow 299 * @param stepId id of step parent 300 * @param actionId id of current action 301 * @param conditionNodeId id of selected node 302 * @param arguments map of arguments name and values 303 * @return map of condition's info 304 * @throws WorkflowException exception while resolving condition 305 */ 306 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 307 public Map<String, Object> editCondition(String workflowName, Integer stepId, Integer actionId, String conditionNodeId, Map<String, Object> arguments) throws WorkflowException 308 { 309 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 310 _workflowRightHelper.checkEditRight(workflowDescriptor); 311 312 ConditionDescriptor condition = _getCondition(workflowDescriptor, actionId, conditionNodeId); 313 _updateArguments(condition, arguments); 314 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 315 316 ActionDescriptor action = workflowDescriptor.getAction(actionId); 317 return _getConditionProperties(workflowDescriptor, action, condition, stepId, conditionNodeId); 318 } 319 320 private Map<String, Object> _getConditionProperties(WorkflowDescriptor workflow, ActionDescriptor action, ConditionDescriptor condition, Integer stepId, String conditionNodeId) 321 { 322 Map<String, Object> results = new HashMap<>(); 323 results.put("conditionId", condition.getArgs().get("id")); 324 results.put("nodeId", conditionNodeId); 325 results.put("actionId", action.getId()); 326 results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action)); 327 results.put("stepId", stepId); 328 results.put("stepLabel", _workflowStepDAO.getStepLabel(workflow, stepId)); 329 results.put("workflowId", workflow.getName()); 330 return results; 331 } 332 333 private void _updateArguments(ConditionDescriptor condition, Map<String, Object> arguments) 334 { 335 Map<String, String> args = condition.getArgs(); 336 args.clear(); 337 args.putAll(_getConditionParamsValuesAsString(arguments)); 338 } 339 340 /** 341 * Add a new condition 342 * @param workflowName unique name of current workflow 343 * @param stepId id of step parent 344 * @param actionId id of current action 345 * @param parentNodeId id of selected node : can be null if no node is selected 346 * @param arguments map of arguments name and values 347 * @return map of condition's info 348 */ 349 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 350 public Map<String, Object> addCondition(String workflowName, Integer stepId, Integer actionId, String parentNodeId, Map<String, Object> arguments) 351 { 352 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 353 _workflowRightHelper.checkEditRight(workflowDescriptor); 354 355 ActionDescriptor action = workflowDescriptor.getAction(actionId); 356 357 boolean isResultCondition = !StringUtils.isBlank(parentNodeId) && parentNodeId.startsWith(__STEP_RESULT_PREFIX); 358 String computedParentNodeId = StringUtils.isBlank(parentNodeId) 359 ? __AND_ROOT_OPERATOR 360 : isResultCondition && parentNodeId.split("-").length == 1 361 ? parentNodeId + "-" + __AND_ROOT_OPERATOR 362 : parentNodeId; 363 ConditionsDescriptor rootOperator = isResultCondition 364 ? _getResultConditionsRootOperator(workflowName, stepId, actionId, parentNodeId, action) 365 : _getConditionsRootOperator(workflowName, stepId, actionId, action, parentNodeId); 366 367 ConditionsDescriptor currentParentOperator = _getConditionsNode(rootOperator, computedParentNodeId); 368 if (StringUtils.isBlank(currentParentOperator.getType())) 369 { 370 currentParentOperator.setType(AND); 371 } 372 373 374 List<AbstractDescriptor> conditions = currentParentOperator.getConditions(); 375 ConditionDescriptor conditionDescriptor = _createCondition(arguments); 376 conditions.add(conditionDescriptor); 377 378 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 379 380 String conditionNodeId = computedParentNodeId + "-" + "condition" + (conditions.size() - 1); 381 return _getConditionProperties(workflowDescriptor, action, conditionDescriptor, stepId, conditionNodeId); 382 } 383 384 private ConditionDescriptor _createCondition(Map<String, Object> arguments) 385 { 386 DescriptorFactory factory = new DescriptorFactory(); 387 ConditionDescriptor conditionDescriptor = factory.createConditionDescriptor(); 388 conditionDescriptor.setType("avalon"); 389 _updateArguments(conditionDescriptor, arguments); 390 return conditionDescriptor; 391 } 392 393 /** 394 * Get the list of arguments as String, parse multiple arguments 395 * @param params List of condition arguments with values 396 * @return the map of arguments formated for condition descriptor 397 */ 398 protected Map<String, String> _getConditionParamsValuesAsString(Map<String, Object> params) 399 { 400 String conditionId = (String) params.get(__ATTRIBUTE_CONDITIONS_LIST); 401 EnhancedCondition enhancedCondition = _enhancedConditionExtensionPoint.getExtension(conditionId); 402 403 Map<String, String> conditionParams = new HashMap<>(); 404 conditionParams.put("id", conditionId); 405 List<WorkflowArgument> arguments = enhancedCondition.getArguments(); 406 if (!arguments.isEmpty()) 407 { 408 for (WorkflowArgument argument : arguments) 409 { 410 String paramKey = conditionId + "-" + argument.getName(); 411 String paramValue = argument.isMultiple() 412 ? _getListAsString(params, paramKey) 413 : (String) params.get(paramKey); 414 if (!StringUtils.isBlank(paramValue)) 415 { 416 conditionParams.put(argument.getName(), paramValue); 417 } 418 } 419 } 420 return conditionParams; 421 } 422 423 @SuppressWarnings("unchecked") 424 private String _getListAsString(Map<String, Object> params, String paramKey) 425 { 426 List<String> values = (List<String>) params.get(paramKey); 427 return values == null ? "" : String.join(",", values); 428 } 429 430 /** 431 * Get the root operator for regular conditions 432 * @param workflowName name of current workflow 433 * @param stepId id of step parent 434 * @param actionId id of current action 435 * @param action the current action 436 * @param parentNodeId id of the parent node 437 * @return the root conditions operator 438 */ 439 protected ConditionsDescriptor _getConditionsRootOperator(String workflowName, Integer stepId, Integer actionId, ActionDescriptor action, String parentNodeId) 440 { 441 RestrictionDescriptor restriction = action.getRestriction(); 442 if (StringUtils.isBlank(parentNodeId)) //root 443 { 444 if (restriction == null) 445 { 446 addOperator(workflowName, stepId, actionId, parentNodeId, AND, false); 447 restriction = action.getRestriction(); 448 } 449 } 450 451 return restriction.getConditionsDescriptor(); 452 } 453 454 /** 455 * Get the root operator for result conditions 456 * @param workflowName name of current workflow 457 * @param stepId id of step parent 458 * @param actionId id of current action 459 * @param action the current action 460 * @param parentNodeId id of the parent node 461 * @return the root conditions operator 462 */ 463 protected ConditionsDescriptor _getResultConditionsRootOperator(String workflowName, Integer stepId, Integer actionId, String parentNodeId, ActionDescriptor action) 464 { 465 int resultStepId = _getStepId(parentNodeId); 466 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 467 List<ConditionsDescriptor> resultConditions = _workflowResultDAO.getRootResultConditions(conditionalResults, resultStepId); 468 if (resultConditions.isEmpty()) 469 { 470 addOperator(workflowName, stepId, actionId, parentNodeId, AND, true); 471 resultConditions = _workflowResultDAO.getRootResultConditions(conditionalResults, resultStepId); 472 } 473 474 return resultConditions.get(0); 475 } 476 477 /** 478 * Add an operator 479 * @param workflowName unique name of current workflow 480 * @param stepId id of step parent 481 * @param actionId id of current action 482 * @param nodeId id of selected node 483 * @param operatorType the workflow conditions' type. Can be AND or OR 484 * @param isResultCondition true if parent is a conditional result 485 * @return map of the operator's infos 486 */ 487 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 488 public Map<String, Object> addOperator(String workflowName, Integer stepId, Integer actionId, String nodeId, String operatorType, boolean isResultCondition) 489 { 490 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 491 _workflowRightHelper.checkEditRight(workflowDescriptor); 492 493 ActionDescriptor action = workflowDescriptor.getAction(actionId); 494 495 DescriptorFactory factory = new DescriptorFactory(); 496 ConditionsDescriptor newOperator = factory.createConditionsDescriptor(); 497 newOperator.setType(operatorType); 498 boolean isRoot = StringUtils.isBlank(nodeId); 499 500 String newNodeId = ""; 501 if (isResultCondition) 502 { 503 String[] path = _workflowResultDAO.getPath(nodeId); 504 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 505 List<ConditionsDescriptor> resultConditions = _workflowResultDAO.getRootResultConditions(conditionalResults, _getStepId(nodeId)); 506 if (resultConditions.isEmpty())//root 507 { 508 resultConditions.add(newOperator); 509 newNodeId = nodeId + "-" + operatorType.toLowerCase() + "0"; 510 } 511 else 512 { 513 isRoot = path.length == 1; 514 ConditionsDescriptor rootOperator = resultConditions.get(0); 515 516 ConditionsDescriptor currentOperator = isRoot ? rootOperator : _getConditionsNode(rootOperator, nodeId); 517 List<AbstractDescriptor> conditions = currentOperator.getConditions(); 518 conditions.add(newOperator); 519 String parentNodePrefix = isRoot ? nodeId + "-" + __AND_ROOT_OPERATOR : nodeId; 520 newNodeId = parentNodePrefix + "-" + operatorType.toLowerCase() + (conditions.size() - 1); 521 } 522 } 523 else 524 { 525 if (isRoot && action.getRestriction() == null) //root with no condition underneath 526 { 527 RestrictionDescriptor restriction = new RestrictionDescriptor(); 528 restriction.setConditionsDescriptor(newOperator); 529 action.setRestriction(restriction); 530 newNodeId = operatorType.toLowerCase() + "0"; 531 } 532 else 533 { 534 RestrictionDescriptor restriction = action.getRestriction(); 535 ConditionsDescriptor rootOperator = restriction.getConditionsDescriptor(); 536 537 ConditionsDescriptor currentOperator = isRoot ? rootOperator : _getConditionsNode(rootOperator, nodeId); 538 List<AbstractDescriptor> conditions = currentOperator.getConditions(); 539 conditions.add(newOperator); 540 String parentNodePrefix = isRoot ? __AND_ROOT_OPERATOR : nodeId; 541 newNodeId = parentNodePrefix + "-" + operatorType.toLowerCase() + (conditions.size() - 1); 542 } 543 } 544 545 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 546 547 return _getOperatorProperties(workflowDescriptor, action, stepId, operatorType, newNodeId); 548 } 549 550 private Map<String, Object> _getOperatorProperties(WorkflowDescriptor workflowDescriptor, ActionDescriptor action, Integer stepId, String operatorType, String newNodeId) 551 { 552 Map<String, Object> results = new HashMap<>(); 553 results.put("nodeId", newNodeId); 554 results.put("type", operatorType); 555 results.put("actionId", action.getId()); 556 results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action)); 557 results.put("stepId", stepId); 558 results.put("stepLabel", _workflowStepDAO.getStepLabel(workflowDescriptor, stepId)); 559 results.put("workflowId", workflowDescriptor.getName()); 560 return results; 561 } 562 563 private Integer _getStepId(String nodeId) 564 { 565 String[] path = _workflowResultDAO.getPath(nodeId); 566 return Integer.valueOf(path[0].substring(4)); 567 } 568 569 private ConditionsDescriptor _getConditionsNode(ConditionsDescriptor rootOperator, String nodeId) 570 { 571 ConditionsDescriptor currentOperator = rootOperator; 572 573 List<AbstractDescriptor> conditions = rootOperator.getConditions(); 574 String[] path = _workflowResultDAO.getPath(nodeId); 575 boolean isResultCondition = path[0].startsWith(__STEP_RESULT_PREFIX); 576 if (!isResultCondition && path.length > 1 || isResultCondition && path.length > 2) //current node is not root direct child 577 { 578 // get conditions for current node 579 int i = isResultCondition ? 2 : 1; 580 do 581 { 582 int currentOrAndConditionIndex = _getConditionIndex(path, i); 583 584 currentOperator = (ConditionsDescriptor) conditions.get(currentOrAndConditionIndex); 585 conditions = currentOperator.getConditions(); 586 i++; 587 } 588 while (i < path.length); 589 } 590 return currentOperator; 591 } 592 593 private int _getConditionIndex(String[] path, int conditionIndex) 594 { 595 String currentConditionId = path[conditionIndex]; 596 int prefixSize = currentConditionId.startsWith("and") 597 ? "and".length() 598 : currentConditionId.startsWith("or") 599 ? "or".length() 600 : "condition".length(); 601 return Integer.valueOf(currentConditionId.substring(prefixSize)); 602 } 603 604 /** 605 * Delete operator from action 606 * @param workflowName unique name of current workflow 607 * @param stepId id of step parent 608 * @param actionId id of current action 609 * @param nodeId id of selected node 610 * @return map of operator's infos 611 */ 612 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 613 public Map<String, Object> deleteOperator(String workflowName, Integer stepId, Integer actionId, String nodeId) 614 { 615 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 616 _workflowRightHelper.checkEditRight(workflowDescriptor); 617 618 ActionDescriptor action = workflowDescriptor.getAction(actionId); 619 620 String[] path = _workflowResultDAO.getPath(nodeId); 621 boolean isResultCondition = nodeId.startsWith(__STEP_RESULT_PREFIX); 622 if (isResultCondition) 623 { 624 _removeResultConditionOperator(nodeId, action, path); 625 } 626 else 627 { 628 _removeConditionOperator(nodeId, action, path); 629 } 630 631 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 632 633 String type = path[path.length - 1].startsWith("and") ? AND : OR; 634 return _getOperatorProperties(workflowDescriptor, action, stepId, type, nodeId); 635 } 636 637 /** 638 * Remove a regular condition operator 639 * @param nodeId id of operator to remove 640 * @param action current action 641 * @param path the path to current node 642 */ 643 protected void _removeConditionOperator(String nodeId, ActionDescriptor action, String[] path) 644 { 645 if (path.length > 1) 646 { 647 ConditionsDescriptor rootCondition = action.getRestriction().getConditionsDescriptor(); 648 _removeCondition(rootCondition, nodeId, path); 649 } 650 else 651 { 652 action.setRestriction(null); 653 } 654 } 655 656 /** 657 * Remove a result condition operator 658 * @param nodeId id of operator to remove 659 * @param action current action 660 * @param path the path to current node 661 */ 662 protected void _removeResultConditionOperator(String nodeId, ActionDescriptor action, String[] path) 663 { 664 if (path.length > 2) 665 { 666 ConditionsDescriptor rootCondition = (ConditionsDescriptor) _workflowResultDAO.getRootResultConditions(action.getConditionalResults(), _getStepId(nodeId)).get(0); 667 _removeCondition(rootCondition, nodeId, path); 668 } 669 else 670 { 671 Optional parentConditionalResult = action.getConditionalResults().stream() 672 .filter(cr -> ((ConditionalResultDescriptor) cr).getStep() == _getStepId(nodeId)) 673 .findFirst(); 674 if (parentConditionalResult.isPresent()) 675 { 676 ((ConditionalResultDescriptor) parentConditionalResult.get()).getConditions().clear(); 677 } 678 } 679 } 680 681 /** 682 * Delete the condition 683 * @param workflowName unique name of current workflow 684 * @param stepId id of step parent 685 * @param actionId id of current action 686 * @param nodeId id of selected node 687 * @return map of the condition's infos 688 */ 689 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 690 public Map<String, Object> deleteCondition(String workflowName, Integer stepId, Integer actionId, String nodeId) 691 { 692 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 693 _workflowRightHelper.checkEditRight(workflowDescriptor); 694 695 ActionDescriptor action = workflowDescriptor.getAction(actionId); 696 697 String[] path = _workflowResultDAO.getPath(nodeId); 698 boolean isResultCondition = nodeId.startsWith(__STEP_RESULT_PREFIX); 699 ConditionsDescriptor rootCondition = isResultCondition 700 ? (ConditionsDescriptor) _workflowResultDAO.getRootResultConditions(action.getConditionalResults(), _getStepId(nodeId)).get(0) 701 : action.getRestriction().getConditionsDescriptor(); 702 703 int lastIndexOf = nodeId.lastIndexOf('-'); 704 String parentNodeId = nodeId.substring(0, lastIndexOf); 705 ConditionsDescriptor parentNode = _getConditionsNode(rootCondition, parentNodeId); 706 int nodeToDeleteIndex = _getConditionIndex(path, path.length - 1); 707 List conditions = parentNode.getConditions(); 708 ConditionDescriptor conditionToRemove = (ConditionDescriptor) conditions.get(nodeToDeleteIndex); 709 conditions.remove(conditionToRemove); 710 711 if (conditions.isEmpty() && (path.length == 1 || path.length == 2 && path[0].startsWith(AND))) //if only an operator "and' is left 712 { 713 action.setRestriction(null); 714 } 715 716 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 717 718 return _getConditionProperties(workflowDescriptor, action, conditionToRemove, stepId, nodeId); 719 } 720 721 /** 722 * Remove a condition 723 * @param rootCondition the first parent condition operator 724 * @param conditionNodeId id of the current condition 725 * @param conditionPath the full path to the condition 726 * @return the list of conditions after removal 727 */ 728 protected List _removeCondition(ConditionsDescriptor rootCondition, String conditionNodeId, String[] conditionPath) 729 { 730 int lastIndexOf = conditionNodeId.lastIndexOf('-'); 731 String parentNodeId = conditionNodeId.substring(0, lastIndexOf); 732 ConditionsDescriptor parentNode = _getConditionsNode(rootCondition, parentNodeId); 733 int nodeToDeleteIndex = _getConditionIndex(conditionPath, conditionPath.length - 1); 734 List conditions = parentNode.getConditions(); 735 conditions.remove(nodeToDeleteIndex); 736 737 return conditions; 738 } 739 740 /** 741 * Get the tree's condition nodes 742 * @param currentNode id of the current node 743 * @param workflowName unique name of current workflow 744 * @param actionId id of current action 745 * @return a map of current node's children 746 */ 747 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 748 public Map<String, Object> getConditionNodes(String currentNode, String workflowName, Integer actionId) 749 { 750 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 751 List<Map<String, Object>> nodes = new ArrayList<>(); 752 if (_workflowRightHelper.canRead(workflowDescriptor)) 753 { 754 ActionDescriptor action = workflowDescriptor.getAction(actionId); 755 List<AbstractDescriptor> conditions = _getConditions(currentNode, action); 756 if (!conditions.isEmpty()) 757 { 758 boolean rootIsAND = !action.getRestriction().getConditionsDescriptor().getType().equals(OR); 759 for (int i = 0; i < conditions.size(); i++) 760 { 761 nodes.add(conditionToJSON(conditions.get(i), currentNode, i, rootIsAND)); 762 } 763 } 764 } 765 766 return Map.of("conditions", nodes); 767 } 768 769 /** 770 * Get conditions below current node 771 * @param currentNode id of the current node 772 * @param action current action 773 * @return a list of childnodes condition 774 */ 775 protected List<AbstractDescriptor> _getConditions(String currentNode, ActionDescriptor action) 776 { 777 RestrictionDescriptor restriction = action.getRestriction(); 778 if (restriction != null) 779 { 780 ConditionsDescriptor rootConditionsDescriptor = restriction.getConditionsDescriptor(); 781 782 String[] path = _workflowResultDAO.getPath(currentNode); 783 // The current node is root and it's a OR node, so display it ... 784 if ("root".equals(currentNode) && rootConditionsDescriptor.getType().equals(OR)) 785 { 786 return List.of(rootConditionsDescriptor); 787 } 788 // ... the current node is a AND node, display child conditions ... 789 else if (path.length == 1) 790 { 791 return rootConditionsDescriptor.getConditions(); 792 } 793 // ... the selected node is not a condition, so it has children 794 // we need to search the condition and get child condition of current node 795 else if (!path[path.length - 1].startsWith("condition")) 796 { 797 List<AbstractDescriptor> conditions = rootConditionsDescriptor.getConditions(); 798 // get conditions for current node 799 int i = 1; 800 do 801 { 802 String currentOrAndConditionId = path[i]; 803 int currentOrAndConditionIndex = (currentOrAndConditionId.startsWith("and")) 804 ? Integer.valueOf(currentOrAndConditionId.substring(3)) 805 : Integer.valueOf(currentOrAndConditionId.substring(2)); 806 807 ConditionsDescriptor currentOrAndCondition = (ConditionsDescriptor) conditions.get(currentOrAndConditionIndex); 808 conditions = currentOrAndCondition.getConditions(); 809 i++; 810 } 811 while (i < path.length); 812 813 return conditions; 814 } 815 } 816 817 return List.of(); 818 } 819 820 /** 821 * Get condition or condition types properties 822 * @param condition current condition, can be ConditionsDescriptor or ConditionDescriptor 823 * @param currentNodeId the id of the current node in the ConditionTreePanel 824 * @param index index of current condition in node's condition list 825 * @param rootIsAND true if root node is an "AND" condition (in which case it's hidden and has no id) 826 * @return a map of the condition infos 827 */ 828 public Map<String, Object> conditionToJSON(AbstractDescriptor condition, String currentNodeId, int index, boolean rootIsAND) 829 { 830 Map<String, Object> infosConditions = new HashMap<>(); 831 boolean isResult = currentNodeId.startsWith(__STEP_RESULT_PREFIX); 832 boolean isRoot = __ROOT_RESULT_ID.equals(currentNodeId) 833 || isResult && _workflowResultDAO.getPath(currentNodeId).length == 1; 834 String prefix = isResult && isRoot ? currentNodeId + "-" : ""; 835 prefix += isRoot && rootIsAND ? __AND_ROOT_OPERATOR : isRoot ? __OR_ROOT_OPERATOR : currentNodeId; 836 // if it's a 'and' or a 'or' 837 if (condition instanceof ConditionsDescriptor operator) 838 { 839 String type = ((ConditionsDescriptor) condition).getType(); 840 if (!type.equals(OR)) 841 { 842 String id = prefix + "-and" + index; 843 infosConditions.put("id", id); 844 I18nizableText i18nLabel = new I18nizableText("plugin.workflow", __ANDI18N); 845 infosConditions.put("label", i18nLabel); 846 } 847 else 848 { 849 String id = isRoot && !rootIsAND ? prefix : prefix + "-or" + index; 850 infosConditions.put("id", id); 851 I18nizableText i18nLabel = new I18nizableText("plugin.workflow", __ORI18N); 852 infosConditions.put("label", i18nLabel); 853 } 854 infosConditions.put("hasChildren", !operator.getConditions().isEmpty()); 855 } 856 else //it's a condition 857 { 858 String id = prefix + "-condition" + index; 859 infosConditions.put("id", id); 860 infosConditions.put("conditionId", ((ConditionDescriptor) condition).getArgs().get("id")); 861 infosConditions.put("label", _getConditionLabel((ConditionDescriptor) condition)); 862 infosConditions.put("hasChildren", false); 863 } 864 865 return infosConditions; 866 } 867 868 /** 869 * Get current condition's label 870 * @param workflowName unique name of current workflow 871 * @param actionId id of current action 872 * @param nodeId id of selected node 873 * @return the label 874 */ 875 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 876 public I18nizableText getConditionLabel(String workflowName, Integer actionId, String nodeId) 877 { 878 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 879 _workflowRightHelper.checkReadRight(workflowDescriptor); 880 ConditionDescriptor condition = _getCondition(workflowDescriptor, actionId, nodeId); 881 return _getConditionLabel(condition); 882 } 883 884 /** 885 * Get if current operator has children conditions 886 * @param workflowName unique name of current workflow 887 * @param actionId id of current action 888 * @param nodeId id of selected operator node 889 * @return true if operator has children 890 */ 891 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 892 public boolean hasChildConditions(String workflowName, Integer actionId, String nodeId) 893 { 894 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 895 _workflowRightHelper.checkReadRight(workflowDescriptor); 896 897 ActionDescriptor action = workflowDescriptor.getAction(actionId); 898 List<AbstractDescriptor> conditions = nodeId.startsWith("step") 899 ? _workflowResultDAO.getChildrenResultConditions(nodeId, action, action.getConditionalResults()) 900 : _getConditions(nodeId, action); 901 return !conditions.isEmpty(); 902 } 903 904 /** 905 * Get condition's description or id 906 * @param condition the current condition 907 * @return the condition description if exist, or its id if not 908 */ 909 protected I18nizableText _getConditionLabel(ConditionDescriptor condition) 910 { 911 String id = (String) condition.getArgs().get("id"); 912 TypeResolver typeResolver = new AvalonTypeResolver(_manager); 913 try 914 { 915 Condition function = typeResolver.getCondition(condition.getType(), condition.getArgs()); 916 if (function instanceof EnhancedCondition enhancedCondition) 917 { 918 List<WorkflowArgument> arguments = enhancedCondition.getArguments(); 919 Map<String, String> values = new HashMap<>(); 920 for (WorkflowArgument arg : arguments) 921 { 922 values.put(arg.getName(), (String) condition.getArgs().get(arg.getName())); 923 } 924 I18nizableText description = _getDescription(enhancedCondition, values); 925 926 return description != null ? description : new I18nizableText(id); 927 } 928 } 929 catch (WorkflowException e) 930 { 931 getLogger().warn("An error occured while resolving condition with id {}", id, e); 932 } 933 return new I18nizableText(id); 934 } 935 936 private I18nizableText _getDescription(EnhancedCondition function, Map<String, String> values) 937 { 938 try 939 { 940 return function.getFullLabel(values); 941 } 942 catch (Exception e) 943 { 944 getLogger().warn("Can't get description for condition '{}': a parameter might be missing", function.getClass().getName(), e); 945 return null; 946 } 947 } 948}