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