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