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.DefaultDisableConditions; 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.plugin.component.AbstractLogEnabled; 060 061import com.opensymphony.workflow.Condition; 062import com.opensymphony.workflow.TypeResolver; 063import com.opensymphony.workflow.WorkflowException; 064import com.opensymphony.workflow.loader.AbstractDescriptor; 065import com.opensymphony.workflow.loader.ActionDescriptor; 066import com.opensymphony.workflow.loader.ConditionDescriptor; 067import com.opensymphony.workflow.loader.ConditionalResultDescriptor; 068import com.opensymphony.workflow.loader.ConditionsDescriptor; 069import com.opensymphony.workflow.loader.DescriptorFactory; 070import com.opensymphony.workflow.loader.RestrictionDescriptor; 071import com.opensymphony.workflow.loader.WorkflowDescriptor; 072 073/** 074 * DAO for workflow conditions 075 */ 076public class WorkflowConditionDAO extends AbstractLogEnabled implements Component, Serviceable 077{ 078 /** The component role */ 079 public static final String ROLE = WorkflowConditionDAO.class.getName(); 080 081 /** The "and" type of condition */ 082 public static final String AND = "AND"; 083 084 /** The "or" type of condition */ 085 public static final String OR = "OR"; 086 087 /** Key for "and" label in tree */ 088 protected static final String __ANDI18N = "PLUGIN_WORKFLOW_TRANSITION_CONDITIONS_TYPE_AND"; 089 090 /** Key for "or" label in tree */ 091 protected static final String __ORI18N = "PLUGIN_WORKFLOW_TRANSITION_CONDITIONS_TYPE_OR"; 092 093 /** Extension point for workflow arguments data type */ 094 protected static ModelItemTypeExtensionPoint _workflowArgumentDataTypeExtensionPoint; 095 096 private static final String __OR_ROOT_OPERATOR = "or0"; 097 private static final String __AND_ROOT_OPERATOR = "and0"; 098 private static final String __STEP_RESULT_PREFIX = "step"; 099 private static final String __ROOT_RESULT_ID = "root"; 100 private static final String __ATTRIBUTE_CONDITIONS_LIST = "conditions-list"; 101 102 /** The workflow session helper */ 103 protected WorkflowSessionHelper _workflowSessionHelper; 104 105 /** The workflow helper */ 106 protected WorkflowHelper _workflowHelper; 107 108 /** The workflow right helper */ 109 protected WorflowRightHelper _workflowRightHelper; 110 111 /** The workflow result helper */ 112 protected WorkflowResultDAO _workflowResultDAO; 113 114 /** The workflow step DAO */ 115 protected WorkflowStepDAO _workflowStepDAO; 116 117 /** The workflow transition DAO */ 118 protected WorkflowTransitionDAO _workflowTransitionDAO; 119 120 /** Extension point for Conditions */ 121 protected EnhancedConditionExtensionPoint _enhancedConditionExtensionPoint; 122 123 /** The service manager */ 124 protected ServiceManager _manager; 125 126 public void service(ServiceManager smanager) throws ServiceException 127 { 128 _workflowHelper = (WorkflowHelper) smanager.lookup(WorkflowHelper.ROLE); 129 _workflowRightHelper = (WorflowRightHelper) smanager.lookup(WorflowRightHelper.ROLE); 130 _workflowSessionHelper = (WorkflowSessionHelper) smanager.lookup(WorkflowSessionHelper.ROLE); 131 _workflowResultDAO = (WorkflowResultDAO) smanager.lookup(WorkflowResultDAO.ROLE); 132 _workflowStepDAO = (WorkflowStepDAO) smanager.lookup(WorkflowStepDAO.ROLE); 133 _workflowTransitionDAO = (WorkflowTransitionDAO) smanager.lookup(WorkflowTransitionDAO.ROLE); 134 _workflowArgumentDataTypeExtensionPoint = (ModelItemTypeExtensionPoint) smanager.lookup(ModelItemTypeExtensionPoint.ROLE_WORKFLOW); 135 _enhancedConditionExtensionPoint = (EnhancedConditionExtensionPoint) smanager.lookup(EnhancedConditionExtensionPoint.ROLE); 136 _manager = smanager; 137 } 138 139 /** 140 * Get the condition's model items as fields to configure edition form panel 141 * @return the parameters field as Json readable map 142 * @throws ProcessingException exception while saxing view to json 143 */ 144 @Callable(rights = {"Workflow_Right_Edit", "Workflow_Right_Edit_User"}) 145 public Map<String, Object> getConditionsModel() throws ProcessingException 146 { 147 Map<String, Object> response = new HashMap<>(); 148 149 View view = new View(); 150 SimpleViewItemGroup fieldset = new SimpleViewItemGroup(); 151 fieldset.setName("conditions"); 152 153 Set<Pair<String, EnhancedCondition>> conditions = _enhancedConditionExtensionPoint.getAllConditions() 154 .stream() 155 .filter(this::_hasConditionRight) 156 .collect(Collectors.toSet()); 157 ElementDefinition<String> conditionsList = _getConditionsListModelItem(conditions); 158 List<WorkflowArgument> argumentModelItems = _getArgumentModelItems(conditions); 159 160 ViewElement conditionListView = new ViewElement(); 161 conditionListView.setDefinition(conditionsList); 162 fieldset.addViewItem(conditionListView); 163 164 for (WorkflowArgument functionArgument : argumentModelItems) 165 { 166 ViewElement argumentView = new ViewElement(); 167 argumentView.setDefinition(functionArgument); 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 DefaultDisableConditions(); 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.SKIP_BUILTIN_CHECK) 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.SKIP_BUILTIN_CHECK) 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 _workflowRightHelper.checkEditRight(workflowDescriptor); 313 314 ConditionDescriptor condition = _getCondition(workflowDescriptor, actionId, conditionNodeId); 315 _updateArguments(condition, arguments); 316 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 317 318 ActionDescriptor action = workflowDescriptor.getAction(actionId); 319 return _getConditionProperties(workflowDescriptor, action, condition, stepId, conditionNodeId); 320 } 321 322 private Map<String, Object> _getConditionProperties(WorkflowDescriptor workflow, ActionDescriptor action, ConditionDescriptor condition, Integer stepId, String conditionNodeId) 323 { 324 Map<String, Object> results = new HashMap<>(); 325 results.put("conditionId", condition.getArgs().get("id")); 326 results.put("nodeId", conditionNodeId); 327 results.put("actionId", action.getId()); 328 results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action)); 329 results.put("stepId", stepId); 330 results.put("stepLabel", _workflowStepDAO.getStepLabel(workflow, stepId)); 331 results.put("workflowId", workflow.getName()); 332 return results; 333 } 334 335 private void _updateArguments(ConditionDescriptor condition, Map<String, Object> arguments) 336 { 337 Map<String, String> args = condition.getArgs(); 338 args.clear(); 339 args.putAll(_getConditionParamsValuesAsString(arguments)); 340 } 341 342 /** 343 * Add a new condition 344 * @param workflowName unique name of current workflow 345 * @param stepId id of step parent 346 * @param actionId id of current action 347 * @param parentNodeId id of selected node : can be null if no node is selected 348 * @param arguments map of arguments name and values 349 * @return map of condition's info 350 */ 351 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 352 public Map<String, Object> addCondition(String workflowName, Integer stepId, Integer actionId, String parentNodeId, Map<String, Object> arguments) 353 { 354 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 355 _workflowRightHelper.checkEditRight(workflowDescriptor); 356 357 ActionDescriptor action = workflowDescriptor.getAction(actionId); 358 359 boolean isResultCondition = !StringUtils.isBlank(parentNodeId) && parentNodeId.startsWith(__STEP_RESULT_PREFIX); 360 String computedParentNodeId = StringUtils.isBlank(parentNodeId) 361 ? __AND_ROOT_OPERATOR 362 : isResultCondition && parentNodeId.split("-").length == 1 363 ? parentNodeId + "-" + __AND_ROOT_OPERATOR 364 : parentNodeId; 365 ConditionsDescriptor rootOperator = isResultCondition 366 ? _getResultConditionsRootOperator(workflowName, stepId, actionId, parentNodeId, action) 367 : _getConditionsRootOperator(workflowName, stepId, actionId, action, parentNodeId); 368 369 ConditionsDescriptor currentParentOperator = _getConditionsNode(rootOperator, computedParentNodeId); 370 if (StringUtils.isBlank(currentParentOperator.getType())) 371 { 372 currentParentOperator.setType(AND); 373 } 374 375 376 List<AbstractDescriptor> conditions = currentParentOperator.getConditions(); 377 ConditionDescriptor conditionDescriptor = _createCondition(arguments); 378 conditions.add(conditionDescriptor); 379 380 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 381 382 String conditionNodeId = computedParentNodeId + "-" + "condition" + (conditions.size() - 1); 383 return _getConditionProperties(workflowDescriptor, action, conditionDescriptor, stepId, conditionNodeId); 384 } 385 386 private ConditionDescriptor _createCondition(Map<String, Object> arguments) 387 { 388 DescriptorFactory factory = new DescriptorFactory(); 389 ConditionDescriptor conditionDescriptor = factory.createConditionDescriptor(); 390 conditionDescriptor.setType("avalon"); 391 _updateArguments(conditionDescriptor, arguments); 392 return conditionDescriptor; 393 } 394 395 /** 396 * Get the list of arguments as String, parse multiple arguments 397 * @param params List of condition arguments with values 398 * @return the map of arguments formated for condition descriptor 399 */ 400 protected Map<String, String> _getConditionParamsValuesAsString(Map<String, Object> params) 401 { 402 String conditionId = (String) params.get(__ATTRIBUTE_CONDITIONS_LIST); 403 EnhancedCondition enhancedCondition = _enhancedConditionExtensionPoint.getExtension(conditionId); 404 405 Map<String, String> conditionParams = new HashMap<>(); 406 conditionParams.put("id", conditionId); 407 List<WorkflowArgument> arguments = enhancedCondition.getArguments(); 408 if (!arguments.isEmpty()) 409 { 410 for (WorkflowArgument argument : arguments) 411 { 412 String paramKey = conditionId + "-" + argument.getName(); 413 String paramValue = argument.isMultiple() 414 ? _getListAsString(params, paramKey) 415 : (String) params.get(paramKey); 416 if (!StringUtils.isBlank(paramValue)) 417 { 418 conditionParams.put(argument.getName(), paramValue); 419 } 420 } 421 } 422 return conditionParams; 423 } 424 425 @SuppressWarnings("unchecked") 426 private String _getListAsString(Map<String, Object> params, String paramKey) 427 { 428 List<String> values = (List<String>) params.get(paramKey); 429 return values == null ? "" : String.join(",", values); 430 } 431 432 /** 433 * Get the root operator for regular conditions 434 * @param workflowName name of current workflow 435 * @param stepId id of step parent 436 * @param actionId id of current action 437 * @param action the current action 438 * @param parentNodeId id of the parent node 439 * @return the root conditions operator 440 */ 441 protected ConditionsDescriptor _getConditionsRootOperator(String workflowName, Integer stepId, Integer actionId, ActionDescriptor action, String parentNodeId) 442 { 443 RestrictionDescriptor restriction = action.getRestriction(); 444 if (StringUtils.isBlank(parentNodeId)) //root 445 { 446 if (restriction == null) 447 { 448 addOperator(workflowName, stepId, actionId, parentNodeId, AND, false); 449 restriction = action.getRestriction(); 450 } 451 } 452 453 return restriction.getConditionsDescriptor(); 454 } 455 456 /** 457 * Get the root operator for result conditions 458 * @param workflowName name of current workflow 459 * @param stepId id of step parent 460 * @param actionId id of current action 461 * @param action the current action 462 * @param parentNodeId id of the parent node 463 * @return the root conditions operator 464 */ 465 protected ConditionsDescriptor _getResultConditionsRootOperator(String workflowName, Integer stepId, Integer actionId, String parentNodeId, ActionDescriptor action) 466 { 467 int resultStepId = _getStepId(parentNodeId); 468 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 469 List<ConditionsDescriptor> resultConditions = _workflowResultDAO.getRootResultConditions(conditionalResults, resultStepId); 470 if (resultConditions.isEmpty()) 471 { 472 addOperator(workflowName, stepId, actionId, parentNodeId, AND, true); 473 resultConditions = _workflowResultDAO.getRootResultConditions(conditionalResults, resultStepId); 474 } 475 476 return resultConditions.get(0); 477 } 478 479 /** 480 * Add an operator 481 * @param workflowName unique name of current workflow 482 * @param stepId id of step parent 483 * @param actionId id of current action 484 * @param nodeId id of selected node 485 * @param operatorType the workflow conditions' type. Can be AND or OR 486 * @param isResultCondition true if parent is a conditional result 487 * @return map of the operator's infos 488 */ 489 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 490 public Map<String, Object> addOperator(String workflowName, Integer stepId, Integer actionId, String nodeId, String operatorType, boolean isResultCondition) 491 { 492 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 493 _workflowRightHelper.checkEditRight(workflowDescriptor); 494 495 ActionDescriptor action = workflowDescriptor.getAction(actionId); 496 497 DescriptorFactory factory = new DescriptorFactory(); 498 ConditionsDescriptor newOperator = factory.createConditionsDescriptor(); 499 newOperator.setType(operatorType); 500 boolean isRoot = StringUtils.isBlank(nodeId); 501 502 String newNodeId = ""; 503 if (isResultCondition) 504 { 505 String[] path = _workflowResultDAO.getPath(nodeId); 506 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 507 List<ConditionsDescriptor> resultConditions = _workflowResultDAO.getRootResultConditions(conditionalResults, _getStepId(nodeId)); 508 if (resultConditions.isEmpty())//root 509 { 510 resultConditions.add(newOperator); 511 newNodeId = nodeId + "-" + operatorType.toLowerCase() + "0"; 512 } 513 else 514 { 515 isRoot = path.length == 1; 516 ConditionsDescriptor rootOperator = resultConditions.get(0); 517 518 ConditionsDescriptor currentOperator = isRoot ? rootOperator : _getConditionsNode(rootOperator, nodeId); 519 List<AbstractDescriptor> conditions = currentOperator.getConditions(); 520 conditions.add(newOperator); 521 String parentNodePrefix = isRoot ? nodeId + "-" + __AND_ROOT_OPERATOR : nodeId; 522 newNodeId = parentNodePrefix + "-" + operatorType.toLowerCase() + (conditions.size() - 1); 523 } 524 } 525 else 526 { 527 if (isRoot && action.getRestriction() == null) //root with no condition underneath 528 { 529 RestrictionDescriptor restriction = new RestrictionDescriptor(); 530 restriction.setConditionsDescriptor(newOperator); 531 action.setRestriction(restriction); 532 newNodeId = operatorType.toLowerCase() + "0"; 533 } 534 else 535 { 536 RestrictionDescriptor restriction = action.getRestriction(); 537 ConditionsDescriptor rootOperator = restriction.getConditionsDescriptor(); 538 539 ConditionsDescriptor currentOperator = isRoot ? rootOperator : _getConditionsNode(rootOperator, nodeId); 540 List<AbstractDescriptor> conditions = currentOperator.getConditions(); 541 conditions.add(newOperator); 542 String parentNodePrefix = isRoot ? __AND_ROOT_OPERATOR : nodeId; 543 newNodeId = parentNodePrefix + "-" + operatorType.toLowerCase() + (conditions.size() - 1); 544 } 545 } 546 547 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 548 549 return _getOperatorProperties(workflowDescriptor, action, stepId, operatorType, newNodeId); 550 } 551 552 private Map<String, Object> _getOperatorProperties(WorkflowDescriptor workflowDescriptor, ActionDescriptor action, Integer stepId, String operatorType, String newNodeId) 553 { 554 Map<String, Object> results = new HashMap<>(); 555 results.put("nodeId", newNodeId); 556 results.put("type", operatorType); 557 results.put("actionId", action.getId()); 558 results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action)); 559 results.put("stepId", stepId); 560 results.put("stepLabel", _workflowStepDAO.getStepLabel(workflowDescriptor, stepId)); 561 results.put("workflowId", workflowDescriptor.getName()); 562 return results; 563 } 564 565 private Integer _getStepId(String nodeId) 566 { 567 String[] path = _workflowResultDAO.getPath(nodeId); 568 return Integer.valueOf(path[0].substring(4)); 569 } 570 571 private ConditionsDescriptor _getConditionsNode(ConditionsDescriptor rootOperator, String nodeId) 572 { 573 ConditionsDescriptor currentOperator = rootOperator; 574 575 List<AbstractDescriptor> conditions = rootOperator.getConditions(); 576 String[] path = _workflowResultDAO.getPath(nodeId); 577 boolean isResultCondition = path[0].startsWith(__STEP_RESULT_PREFIX); 578 if (!isResultCondition && path.length > 1 || isResultCondition && path.length > 2) //current node is not root direct child 579 { 580 // get conditions for current node 581 int i = isResultCondition ? 2 : 1; 582 do 583 { 584 int currentOrAndConditionIndex = _getConditionIndex(path, i); 585 586 currentOperator = (ConditionsDescriptor) conditions.get(currentOrAndConditionIndex); 587 conditions = currentOperator.getConditions(); 588 i++; 589 } 590 while (i < path.length); 591 } 592 return currentOperator; 593 } 594 595 private int _getConditionIndex(String[] path, int conditionIndex) 596 { 597 String currentConditionId = path[conditionIndex]; 598 int prefixSize = currentConditionId.startsWith("and") 599 ? "and".length() 600 : currentConditionId.startsWith("or") 601 ? "or".length() 602 : "condition".length(); 603 return Integer.valueOf(currentConditionId.substring(prefixSize)); 604 } 605 606 /** 607 * Delete operator from action 608 * @param workflowName unique name of current workflow 609 * @param stepId id of step parent 610 * @param actionId id of current action 611 * @param nodeId id of selected node 612 * @return map of operator's infos 613 */ 614 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 615 public Map<String, Object> deleteOperator(String workflowName, Integer stepId, Integer actionId, String nodeId) 616 { 617 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 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.SKIP_BUILTIN_CHECK) 692 public Map<String, Object> deleteCondition(String workflowName, Integer stepId, Integer actionId, String nodeId) 693 { 694 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 695 _workflowRightHelper.checkEditRight(workflowDescriptor); 696 697 ActionDescriptor action = workflowDescriptor.getAction(actionId); 698 699 String[] path = _workflowResultDAO.getPath(nodeId); 700 boolean isResultCondition = nodeId.startsWith(__STEP_RESULT_PREFIX); 701 ConditionsDescriptor rootCondition = isResultCondition 702 ? (ConditionsDescriptor) _workflowResultDAO.getRootResultConditions(action.getConditionalResults(), _getStepId(nodeId)).get(0) 703 : action.getRestriction().getConditionsDescriptor(); 704 705 int lastIndexOf = nodeId.lastIndexOf('-'); 706 String parentNodeId = nodeId.substring(0, lastIndexOf); 707 ConditionsDescriptor parentNode = _getConditionsNode(rootCondition, parentNodeId); 708 int nodeToDeleteIndex = _getConditionIndex(path, path.length - 1); 709 List conditions = parentNode.getConditions(); 710 ConditionDescriptor conditionToRemove = (ConditionDescriptor) conditions.get(nodeToDeleteIndex); 711 conditions.remove(conditionToRemove); 712 713 if (conditions.isEmpty() && (path.length == 1 || path.length == 2 && path[0].startsWith(AND))) //if only an operator "and' is left 714 { 715 action.setRestriction(null); 716 } 717 718 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 719 720 return _getConditionProperties(workflowDescriptor, action, conditionToRemove, stepId, nodeId); 721 } 722 723 /** 724 * Remove a condition 725 * @param rootCondition the first parent condition operator 726 * @param conditionNodeId id of the current condition 727 * @param conditionPath the full path to the condition 728 * @return the list of conditions after removal 729 */ 730 protected List _removeCondition(ConditionsDescriptor rootCondition, String conditionNodeId, String[] conditionPath) 731 { 732 int lastIndexOf = conditionNodeId.lastIndexOf('-'); 733 String parentNodeId = conditionNodeId.substring(0, lastIndexOf); 734 ConditionsDescriptor parentNode = _getConditionsNode(rootCondition, parentNodeId); 735 int nodeToDeleteIndex = _getConditionIndex(conditionPath, conditionPath.length - 1); 736 List conditions = parentNode.getConditions(); 737 conditions.remove(nodeToDeleteIndex); 738 739 return conditions; 740 } 741 742 /** 743 * Get the tree's condition nodes 744 * @param currentNode id of the current node 745 * @param workflowName unique name of current workflow 746 * @param actionId id of current action 747 * @return a map of current node's children 748 */ 749 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 750 public Map<String, Object> getConditionNodes(String currentNode, String workflowName, Integer actionId) 751 { 752 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 753 List<Map<String, Object>> nodes = new ArrayList<>(); 754 if (_workflowRightHelper.canRead(workflowDescriptor)) 755 { 756 ActionDescriptor action = workflowDescriptor.getAction(actionId); 757 List<AbstractDescriptor> conditions = _getConditions(currentNode, action); 758 if (!conditions.isEmpty()) 759 { 760 boolean rootIsAND = !action.getRestriction().getConditionsDescriptor().getType().equals(OR); 761 for (int i = 0; i < conditions.size(); i++) 762 { 763 nodes.add(conditionToJSON(conditions.get(i), currentNode, i, rootIsAND)); 764 } 765 } 766 } 767 768 return Map.of("conditions", nodes); 769 } 770 771 /** 772 * Get conditions below current node 773 * @param currentNode id of the current node 774 * @param action current action 775 * @return a list of childnodes condition 776 */ 777 protected List<AbstractDescriptor> _getConditions(String currentNode, ActionDescriptor action) 778 { 779 RestrictionDescriptor restriction = action.getRestriction(); 780 if (restriction != null) 781 { 782 ConditionsDescriptor rootConditionsDescriptor = restriction.getConditionsDescriptor(); 783 784 String[] path = _workflowResultDAO.getPath(currentNode); 785 // The current node is root and it's a OR node, so display it ... 786 if ("root".equals(currentNode) && rootConditionsDescriptor.getType().equals(OR)) 787 { 788 return List.of(rootConditionsDescriptor); 789 } 790 // ... the current node is a AND node, display child conditions ... 791 else if (path.length == 1) 792 { 793 return rootConditionsDescriptor.getConditions(); 794 } 795 // ... the selected node is not a condition, so it has children 796 // we need to search the condition and get child condition of current node 797 else if (!path[path.length - 1].startsWith("condition")) 798 { 799 List<AbstractDescriptor> conditions = rootConditionsDescriptor.getConditions(); 800 // get conditions for current node 801 int i = 1; 802 do 803 { 804 String currentOrAndConditionId = path[i]; 805 int currentOrAndConditionIndex = (currentOrAndConditionId.startsWith("and")) 806 ? Integer.valueOf(currentOrAndConditionId.substring(3)) 807 : Integer.valueOf(currentOrAndConditionId.substring(2)); 808 809 ConditionsDescriptor currentOrAndCondition = (ConditionsDescriptor) conditions.get(currentOrAndConditionIndex); 810 conditions = currentOrAndCondition.getConditions(); 811 i++; 812 } 813 while (i < path.length); 814 815 return conditions; 816 } 817 } 818 819 return List.of(); 820 } 821 822 /** 823 * Get condition or condition types properties 824 * @param condition current condition, can be ConditionsDescriptor or ConditionDescriptor 825 * @param currentNodeId the id of the current node in the ConditionTreePanel 826 * @param index index of current condition in node's condition list 827 * @param rootIsAND true if root node is an "AND" condition (in which case it's hidden and has no id) 828 * @return a map of the condition infos 829 */ 830 public Map<String, Object> conditionToJSON(AbstractDescriptor condition, String currentNodeId, int index, boolean rootIsAND) 831 { 832 Map<String, Object> infosConditions = new HashMap<>(); 833 boolean isResult = currentNodeId.startsWith(__STEP_RESULT_PREFIX); 834 boolean isRoot = __ROOT_RESULT_ID.equals(currentNodeId) 835 || isResult && _workflowResultDAO.getPath(currentNodeId).length == 1; 836 String prefix = isResult && isRoot ? currentNodeId + "-" : ""; 837 prefix += isRoot && rootIsAND ? __AND_ROOT_OPERATOR : isRoot ? __OR_ROOT_OPERATOR : currentNodeId; 838 // if it's a 'and' or a 'or' 839 if (condition instanceof ConditionsDescriptor operator) 840 { 841 String type = ((ConditionsDescriptor) condition).getType(); 842 if (!type.equals(OR)) 843 { 844 String id = prefix + "-and" + index; 845 infosConditions.put("id", id); 846 I18nizableText i18nLabel = new I18nizableText("plugin.workflow", __ANDI18N); 847 infosConditions.put("label", i18nLabel); 848 } 849 else 850 { 851 String id = isRoot && !rootIsAND ? prefix : prefix + "-or" + index; 852 infosConditions.put("id", id); 853 I18nizableText i18nLabel = new I18nizableText("plugin.workflow", __ORI18N); 854 infosConditions.put("label", i18nLabel); 855 } 856 infosConditions.put("hasChildren", !operator.getConditions().isEmpty()); 857 } 858 else //it's a condition 859 { 860 String id = prefix + "-condition" + index; 861 infosConditions.put("id", id); 862 infosConditions.put("conditionId", ((ConditionDescriptor) condition).getArgs().get("id")); 863 infosConditions.put("label", _getConditionLabel((ConditionDescriptor) condition)); 864 infosConditions.put("hasChildren", false); 865 } 866 867 return infosConditions; 868 } 869 870 /** 871 * Get current condition's label 872 * @param workflowName unique name of current workflow 873 * @param actionId id of current action 874 * @param nodeId id of selected node 875 * @return the label 876 */ 877 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 878 public I18nizableText getConditionLabel(String workflowName, Integer actionId, String nodeId) 879 { 880 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 881 _workflowRightHelper.checkReadRight(workflowDescriptor); 882 ConditionDescriptor condition = _getCondition(workflowDescriptor, actionId, nodeId); 883 return _getConditionLabel(condition); 884 } 885 886 /** 887 * Get if current operator has children conditions 888 * @param workflowName unique name of current workflow 889 * @param actionId id of current action 890 * @param nodeId id of selected operator node 891 * @return true if operator has children 892 */ 893 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 894 public boolean hasChildConditions(String workflowName, Integer actionId, String nodeId) 895 { 896 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 897 _workflowRightHelper.checkReadRight(workflowDescriptor); 898 899 ActionDescriptor action = workflowDescriptor.getAction(actionId); 900 List<AbstractDescriptor> conditions = nodeId.startsWith("step") 901 ? _workflowResultDAO.getChildrenResultConditions(nodeId, action, action.getConditionalResults()) 902 : _getConditions(nodeId, action); 903 return !conditions.isEmpty(); 904 } 905 906 /** 907 * Get condition's description or id 908 * @param condition the current condition 909 * @return the condition description if exist, or its id if not 910 */ 911 protected I18nizableText _getConditionLabel(ConditionDescriptor condition) 912 { 913 String id = (String) condition.getArgs().get("id"); 914 TypeResolver typeResolver = new AvalonTypeResolver(_manager); 915 try 916 { 917 Condition function = typeResolver.getCondition(condition.getType(), condition.getArgs()); 918 if (function instanceof EnhancedCondition enhancedCondition) 919 { 920 List<WorkflowArgument> arguments = enhancedCondition.getArguments(); 921 Map<String, String> values = new HashMap<>(); 922 for (WorkflowArgument arg : arguments) 923 { 924 values.put(arg.getName(), (String) condition.getArgs().get(arg.getName())); 925 } 926 I18nizableText description = _getDescription(enhancedCondition, values); 927 928 return description != null ? description : new I18nizableText(id); 929 } 930 } 931 catch (WorkflowException e) 932 { 933 getLogger().warn("An error occured while resolving condition with id {}", id, e); 934 } 935 return new I18nizableText(id); 936 } 937 938 private I18nizableText _getDescription(EnhancedCondition function, Map<String, String> values) 939 { 940 try 941 { 942 return function.getFullLabel(values); 943 } 944 catch (Exception e) 945 { 946 getLogger().warn("Can't get description for condition '{}': a parameter might be missing", function.getClass().getName(), e); 947 return null; 948 } 949 } 950}