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 */ 016package org.ametys.plugins.workflow.dao; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.stream.Collectors; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030 031import org.ametys.core.ui.Callable; 032import org.ametys.core.util.I18nUtils; 033import org.ametys.plugins.workflow.component.WorkflowLanguageManager; 034import org.ametys.plugins.workflow.support.I18nHelper; 035import org.ametys.plugins.workflow.support.WorflowRightHelper; 036import org.ametys.plugins.workflow.support.WorkflowHelper; 037import org.ametys.plugins.workflow.support.WorkflowSessionHelper; 038import org.ametys.runtime.i18n.I18nizableText; 039import org.ametys.runtime.plugin.component.AbstractLogEnabled; 040 041import com.opensymphony.workflow.loader.AbstractDescriptor; 042import com.opensymphony.workflow.loader.ActionDescriptor; 043import com.opensymphony.workflow.loader.ConditionalResultDescriptor; 044import com.opensymphony.workflow.loader.ConditionsDescriptor; 045import com.opensymphony.workflow.loader.DescriptorFactory; 046import com.opensymphony.workflow.loader.ResultDescriptor; 047import com.opensymphony.workflow.loader.StepDescriptor; 048import com.opensymphony.workflow.loader.WorkflowDescriptor; 049 050/** 051 * DAO for workflow steps 052 */ 053public class WorkflowStepDAO extends AbstractLogEnabled implements Component, Serviceable 054{ 055 /** The component's role */ 056 public static final String ROLE = WorkflowStepDAO.class.getName(); 057 058 /** Id for initial step */ 059 public static final String INITIAL_STEP_ID = "step0"; 060 061 /** The default label for steps */ 062 public static final I18nizableText DEFAULT_STEP_NAME = new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_DEFAULT_STEP_LABEL"); 063 064 /** Default path for svg step icons */ 065 private static final String __DEFAULT_SVG_STEP_ICON_PATH = "plugin:cms://resources/img/history/workflow/step_0_16.png"; 066 067 /** Default path for node step icons */ 068 private static final String __DEFAULT_STEP_ICON_PATH = "/plugins/cms/resources/img/history/workflow/step_0_16.png"; 069 070 /** The workflow helper */ 071 protected WorkflowHelper _workflowHelper; 072 073 /** The helper for i18n translations and catalogs */ 074 protected I18nHelper _i18nHelper; 075 076 /** The workflow session helper */ 077 protected WorkflowSessionHelper _workflowSessionHelper; 078 079 /** The workflow right helper */ 080 protected WorflowRightHelper _workflowRightHelper; 081 082 /** The workflow condition DAO */ 083 protected WorkflowConditionDAO _workflowConditionDAO; 084 085 /** The workflow result DAO */ 086 protected WorkflowResultDAO _workflowResultDAO; 087 088 /** The workflow transition DAO */ 089 protected WorkflowTransitionDAO _workflowTransitionDAO; 090 091 /** The workflow language manager */ 092 protected WorkflowLanguageManager _workflowLanguageManager; 093 094 /** I18n Utils */ 095 protected I18nUtils _i18nUtils; 096 097 public void service(ServiceManager smanager) throws ServiceException 098 { 099 _workflowHelper = (WorkflowHelper) smanager.lookup(WorkflowHelper.ROLE); 100 _workflowSessionHelper = (WorkflowSessionHelper) smanager.lookup(WorkflowSessionHelper.ROLE); 101 _workflowRightHelper = (WorflowRightHelper) smanager.lookup(WorflowRightHelper.ROLE); 102 _workflowConditionDAO = (WorkflowConditionDAO) smanager.lookup(WorkflowConditionDAO.ROLE); 103 _workflowTransitionDAO = (WorkflowTransitionDAO) smanager.lookup(WorkflowTransitionDAO.ROLE); 104 _workflowLanguageManager = (WorkflowLanguageManager) smanager.lookup(WorkflowLanguageManager.ROLE); 105 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 106 _i18nHelper = (I18nHelper) smanager.lookup(I18nHelper.ROLE); 107 _workflowResultDAO = (WorkflowResultDAO) smanager.lookup(WorkflowResultDAO.ROLE); 108 } 109 110 /** 111 * Verify that current workflow has steps 112 * @param workflowName the workflow's unique name 113 * @return true if worflow has steps 114 */ 115 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 116 public boolean hasSteps(String workflowName) 117 { 118 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 119 _workflowRightHelper.checkReadRight(workflowDescriptor); 120 return !workflowDescriptor.getSteps().isEmpty(); 121 } 122 123 /** 124 * Get the step editable infos 125 * @param workflowName current workflow's id 126 * @param stepId current step's id 127 * @return a map of step infos and non-available ids 128 */ 129 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 130 public Map<String, Object> getStepInfos(String workflowName, Integer stepId) 131 { 132 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 133 _workflowRightHelper.checkReadRight(workflowDescriptor); 134 Map<String, Object> stepInfos = new HashMap<>(); 135 List<Integer> stepIds = _getUsedStepIds(workflowDescriptor); 136 Map<String, String> translations = new HashMap<>(); 137 if (stepId == null) //creation mode 138 { 139 int id = _getUniqueStepId(workflowDescriptor); 140 stepInfos.put("id", id); 141 translations.put(_workflowLanguageManager.getCurrentLanguage(), _i18nUtils.translate(DEFAULT_STEP_NAME)); 142 } 143 else //edit mode 144 { 145 stepIds.remove(stepId); 146 stepInfos.put("id", stepId); 147 I18nizableText labelKey = getStepLabel(workflowDescriptor, stepId); 148 translations = _workflowSessionHelper.getTranslation(workflowName, labelKey); 149 if (translations == null) 150 { 151 translations = Map.of(_workflowLanguageManager.getCurrentLanguage(), getStepLabelAsString(workflowDescriptor, stepId, false)); 152 } 153 } 154 stepInfos.put("labels", translations); 155 stepInfos.put("ids", stepIds); 156 157 return stepInfos; 158 } 159 160 @SuppressWarnings("unchecked") 161 private List<Integer> _getUsedStepIds(WorkflowDescriptor workflowDescriptor) 162 { 163 List<Integer> usedIds = (List<Integer>) workflowDescriptor.getSteps().stream() 164 .map(s -> ((StepDescriptor) s).getId()) 165 .collect(Collectors.toList()); 166 usedIds.add(0); 167 return usedIds; 168 } 169 170 /** 171 * Create a new step and add it to current workflow 172 * @param workflowName current workflow's id 173 * @param stepId the new step id 174 * @param labels the new step labels 175 * @return map of the step infos 176 */ 177 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 178 public Map<String, Object> createStep(String workflowName, Integer stepId, Map<String, String> labels) 179 { 180 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 181 _workflowRightHelper.checkEditRight(workflowDescriptor); 182 Map<String, Object> results = new HashMap<>(); 183 184 List<Integer> stepIds = _getUsedStepIds(workflowDescriptor); //test if new id is unique between workflow's steps 185 if (stepIds.contains(stepId) || stepId == 0) 186 { 187 results.put("message", "duplicate-id"); 188 return results; 189 } 190 191 DescriptorFactory factory = new DescriptorFactory(); 192 StepDescriptor stepDescriptor = factory.createStepDescriptor(); 193 stepDescriptor.setId(stepId); 194 I18nizableText stepLabelKey = _i18nHelper.generateI18nKey(workflowName, "STEP", stepId); 195 stepDescriptor.setName(stepLabelKey.toString()); 196 workflowDescriptor.addStep(stepDescriptor); 197 198 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 199 _workflowSessionHelper.updateTranslations(workflowName, stepLabelKey, labels); 200 201 results.put("stepId", stepId); 202 results.put("stepLabels", labels); 203 results.put("workflowId", workflowName); 204 205 return results; 206 } 207 208 private int _getUniqueStepId(WorkflowDescriptor workflowDescriptor) 209 { 210 List<Integer> stepIds = _getUsedStepIds(workflowDescriptor); 211 int id = 1; 212 while (stepIds.contains(id)) 213 { 214 id++; 215 } 216 return id; 217 } 218 219 /** 220 * Edit the step label 221 * @param workflowName current workflow's id 222 * @param stepId the step's id 223 * @param newMainLabel the new label in the current application's language 224 * @return map of the step infos if edit worked, contain error message else 225 */ 226 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 227 public Map<String, Object> editStepLabel(String workflowName, Integer stepId, String newMainLabel) 228 { 229 return editStep(workflowName, stepId, stepId, Map.of(_workflowLanguageManager.getCurrentLanguage(), newMainLabel)); 230 } 231 232 /** 233 * Edit the step 234 * @param workflowName current workflow's id 235 * @param oldId the step's last id 236 * @param id the step's new id 237 * @param labels the new step labels 238 * @return map of the step infos if edit worked, contain error message else 239 */ 240 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 241 public Map<String, Object> editStep(String workflowName, Integer oldId, Integer id, Map<String, String> labels) 242 { 243 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 244 _workflowRightHelper.checkEditRight(workflowDescriptor); 245 Map<String, Object> results = new HashMap<>(); 246 247 StepDescriptor stepDescriptor = workflowDescriptor.getStep(oldId); 248 249 if (id != oldId) //if step id has been edited 250 { 251 if (!getIncomingActions(oldId, workflowDescriptor).isEmpty()) //edition on id can't happen if there are transitions leading to this step 252 { 253 results.put("message", "incoming-actions"); 254 return results; 255 } 256 List<Integer> stepIds = _getUsedStepIds(workflowDescriptor); //test if new id is unique between workflow's steps 257 if (stepIds.contains(id)) 258 { 259 results.put("message", "duplicate-id"); 260 return results; 261 } 262 stepDescriptor.setId(id); 263 } 264 265 String defaultCatalog = _workflowHelper.getWorkflowCatalog(workflowName); 266 I18nizableText labelKey = getStepLabel(workflowDescriptor, id); 267 if (!defaultCatalog.equals(labelKey.getCatalogue())) 268 { 269 labelKey = new I18nizableText(defaultCatalog, labelKey.getKey()); 270 String newName = labelKey.toString(); 271 stepDescriptor.setName(newName); 272 } 273 _workflowSessionHelper.updateTranslations(workflowName, labelKey, labels); 274 275 results.put("stepId", id); 276 results.put("oldStepId", oldId); 277 results.put("stepLabels", labels); 278 results.put("workflowId", workflowName); 279 280 return results; 281 } 282 283 /** 284 * Delete the step from workflow 285 * @param workflowName current workflow's id 286 * @param stepId current step's id 287 * @return an error message if deleting couldn't proceed 288 */ 289 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 290 public Map<String, Object> deleteStep(String workflowName, Integer stepId) 291 { 292 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 293 _workflowRightHelper.checkEditRight(workflowDescriptor); 294 Map<String, Object> results = new HashMap<>(); 295 296 StepDescriptor stepDescriptor = workflowDescriptor.getStep(stepId); 297 298 if (getIncomingActions(stepId, workflowDescriptor).isEmpty()) //we can't delete this step if there are transitions having current step as result 299 { 300 I18nizableText stepLabel = getStepLabel(workflowDescriptor, stepId); 301 workflowDescriptor.getSteps().remove(stepDescriptor); 302 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 303 _workflowSessionHelper.removeTranslation(workflowName, getStepLabel(stepDescriptor)); 304 305 results.put("stepId", stepId); 306 results.put("stepLabels", stepLabel); 307 results.put("workflowId", workflowName); 308 } 309 else 310 { 311 results.put("message", "incoming-actions"); 312 } 313 314 return results; 315 } 316 317 /** 318 * Get the step label as new I18nizableText 319 * @param stepDescriptor the current step 320 * @return the step label 321 */ 322 public I18nizableText getStepLabel(StepDescriptor stepDescriptor) 323 { 324 return new I18nizableText("application", stepDescriptor.getName()); 325 } 326 327 328 /** 329 * Get the workflow editor tree's nodes 330 * @param currentNode id of the current node 331 * @param workflowName unique name of current workflow 332 * @return a map of the current node's children 333 */ 334 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 335 public Map<String, Object> getStepNodes(String currentNode, String workflowName) 336 { 337 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 338 _workflowRightHelper.checkReadRight(workflowDescriptor); 339 List<Map<String, Object>> nodes = new ArrayList<>(); 340 boolean canWrite = _workflowRightHelper.canWrite(workflowDescriptor); 341 342 if (workflowName != null) 343 { 344 List<ActionDescriptor> initialActions = workflowDescriptor.getInitialActions(); 345 if (currentNode.equals("root")) 346 { 347 //initial step 348 Map<String, Object> infosInitialStep = _step2JSON(workflowDescriptor, 0, initialActions.size() > 0, false, false); 349 nodes.add(infosInitialStep); 350 351 //other steps 352 List<StepDescriptor> steps = workflowDescriptor.getSteps(); 353 for (StepDescriptor step : steps) 354 { 355 Map<String, Object> infos = _step2JSON(workflowDescriptor, step.getId(), step.getActions().size() > 0, false, canWrite); 356 nodes.add(infos); 357 } 358 } 359 else if (currentNode.equals(INITIAL_STEP_ID)) 360 { 361 //initial actions 362 for (ActionDescriptor initialAction : initialActions) 363 { 364 nodes.add(_action2JSON(currentNode, initialAction, workflowDescriptor, canWrite)); 365 } 366 } 367 else 368 { 369 //regular actions 370 StepDescriptor step = workflowDescriptor.getStep(Integer.valueOf(currentNode.substring("step".length()))); 371 for (ActionDescriptor transition : (List<ActionDescriptor>) step.getActions()) 372 { 373 nodes.add(_action2JSON(currentNode, transition, workflowDescriptor, canWrite)); 374 } 375 } 376 } 377 378 return Map.of("steps", nodes); 379 } 380 381 /** 382 * Get the action infos for tree panel node 383 * @param stepId id of current step node 384 * @param action currently processed action 385 * @param workflowDescriptor current workflow 386 * @param canWrite true if current user has edition right on current workflow 387 * @return map of the action infos 388 */ 389 protected Map<String, Object> _action2JSON(String stepId, ActionDescriptor action, WorkflowDescriptor workflowDescriptor, boolean canWrite) 390 { 391 Set<StepWithIcon> finalSteps = _getActionFinalSteps(action, workflowDescriptor); 392 Map<Integer, Object> finalStepNames = finalSteps.stream().collect(Collectors.toMap(s -> s.id(), s -> s.label())); 393 Map<Integer, Object> finalStepIcons = finalSteps.stream().collect(Collectors.toMap(s -> s.id(), s -> s.iconPath())); 394 395 String iconPath = _workflowTransitionDAO.getActionIconPath(workflowDescriptor.getName(), action); 396 397 Map<String, Object> infos = new HashMap<>(); 398 399 infos.put("id", stepId + "-action" + action.getId()); 400 infos.put("elementId", action.getId()); 401 infos.put("smallIcon", iconPath); 402 infos.put("label", _workflowTransitionDAO.getActionLabel(workflowDescriptor.getName(), action)); 403 infos.put("elementType", "action"); 404 infos.put("hasChildren", false); 405 infos.put("targetedStepNames", finalStepNames); 406 infos.put("targetedStepIcons", finalStepIcons); 407 infos.put("canWrite", canWrite); 408 409 return infos; 410 } 411 412 /** 413 * Get the conditional and unconditional results of current action 414 * @param action the current action 415 * @param workflowDescriptor the current workflow 416 * @return a list of the final steps as (stepId, stepLabel, StepIconPath) 417 */ 418 protected Set<StepWithIcon> _getActionFinalSteps(ActionDescriptor action, WorkflowDescriptor workflowDescriptor) 419 { 420 Set<StepWithIcon> steps = new HashSet<>(); 421 for (StepDescriptor step : getOutgoingSteps(action, workflowDescriptor)) 422 { 423 int stepId = step.getId(); 424 steps.add(new StepWithIcon( 425 stepId, 426 getStepLabelAsString(workflowDescriptor, stepId, false), 427 getStepIconPath(workflowDescriptor, stepId)) 428 ); 429 } 430 431 return steps; 432 } 433 434 /** 435 * Get possible outgoing steps for action 436 * @param action the current action 437 * @param workflowDescriptor the current workflow 438 * @return a set of the outgoing steps 439 */ 440 public Set<StepDescriptor> getOutgoingSteps(ActionDescriptor action, WorkflowDescriptor workflowDescriptor) 441 { 442 Set<StepDescriptor> outgoingSteps = new HashSet<>(); 443 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 444 boolean hasSameStepTarget = false; 445 if (unconditionalResult.getStep() != -1) 446 { 447 StepDescriptor unconditionalStep = workflowDescriptor.getStep(unconditionalResult.getStep()); 448 outgoingSteps.add(unconditionalStep); 449 } 450 else 451 { 452 hasSameStepTarget = true; 453 } 454 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 455 for (ConditionalResultDescriptor result : conditionalResults) 456 { 457 StepDescriptor conditionalStep = workflowDescriptor.getStep(result.getStep()); 458 if (conditionalStep != null) 459 { 460 outgoingSteps.add(conditionalStep); 461 } 462 else 463 { 464 hasSameStepTarget = true; 465 } 466 } 467 if (hasSameStepTarget) 468 { 469 outgoingSteps.addAll(getIncomingSteps(action.getId(), workflowDescriptor)); 470 } 471 472 return outgoingSteps; 473 } 474 475 /** 476 * Get possible incoming steps for action 477 * @param actionId the current action's id 478 * @param workflowDescriptor the current workflow 479 * @return a set of the action's incoming steps 480 */ 481 public Set<StepDescriptor> getIncomingSteps(int actionId, WorkflowDescriptor workflowDescriptor) 482 { 483 Set<StepDescriptor> incomingSteps = new HashSet<>(); 484 List<StepDescriptor> steps = workflowDescriptor.getSteps(); 485 for (StepDescriptor step : steps) 486 { 487 if (step.getAction(actionId) != null) 488 { 489 incomingSteps.add(step); 490 } 491 } 492 return incomingSteps; 493 } 494 495 /** 496 * Get current action's final steps and associated conditions 497 * @param currentNode id of current node 498 * @param workflowName unique name of current workflow 499 * @param actionId id of current action 500 * @return a map of current node's children 501 */ 502 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 503 public Map<String, Object> getFinalSteps(String currentNode, String workflowName, Integer actionId) 504 { 505 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 506 List<Map<String, Object>> nodes = new ArrayList<>(); 507 if (_workflowRightHelper.canRead(workflowDescriptor)) 508 { 509 ActionDescriptor action = workflowDescriptor.getAction(actionId); 510 boolean canWrite = _workflowRightHelper.canWrite(workflowDescriptor); 511 512 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 513 514 if (currentNode.equals("root")) //get results(steps) nodes 515 { 516 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 517 int stepId = unconditionalResult.getStep(); 518 Map<String, Object> step2json = _step2JSON(workflowDescriptor, stepId, false, stepId != -1, canWrite); 519 step2json.put("isConditional", false); 520 nodes.add(step2json); 521 522 for (ConditionalResultDescriptor result : conditionalResults) 523 { 524 stepId = result.getStep(); 525 Map<String, Object> conditionalStep2Json = _step2JSON(workflowDescriptor, stepId, !result.getConditions().isEmpty(), stepId != -1, canWrite); 526 conditionalStep2Json.put("isConditional", true); 527 nodes.add(conditionalStep2Json); 528 } 529 } 530 else //get conditions nodes, 531 { 532 //conditions to display 533 List<AbstractDescriptor> conditions = _workflowResultDAO.getChildrenResultConditions(currentNode, action, conditionalResults); 534 if (!conditions.isEmpty()) 535 { 536 String[] path = _workflowResultDAO.getPath(currentNode); 537 int stepId = Integer.valueOf(path[0].substring(4)); 538 ConditionsDescriptor rootOperator = _workflowResultDAO.getRootResultConditions(conditionalResults, stepId).get(0); 539 boolean rootIsAND = !rootOperator.getType().equals(WorkflowConditionDAO.OR); 540 for (int i = 0; i < conditions.size(); i++) 541 { 542 nodes.add(_workflowConditionDAO.conditionToJSON(conditions.get(i), currentNode, i, rootIsAND)); 543 } 544 } 545 } 546 } 547 548 return Map.of("results", nodes); 549 } 550 551 /** 552 * Get step infos 553 * @param workflowDescriptor current workflow 554 * @param stepId id of current step 555 * @param hasChildren true if step has actions 556 * @param showId true if id needs to be displayed in the label 557 * @param canWrite true if current user has edition right on current workflow 558 * @return a map of the step infos 559 */ 560 protected Map<String, Object> _step2JSON(WorkflowDescriptor workflowDescriptor, int stepId, boolean hasChildren, boolean showId, boolean canWrite) 561 { 562 Map<String, Object> infos = new HashMap<>(); 563 564 infos.put("id", "step" + stepId); 565 infos.put("elementId", stepId); 566 infos.put("label", getStepLabelAsString(workflowDescriptor, stepId, showId)); 567 infos.put("elementType", "step"); 568 infos.put("hasChildren", hasChildren); 569 infos.put("canWrite", canWrite); 570 try 571 { 572 infos.put("smallIcon", getStepIconPath(workflowDescriptor, stepId)); 573 } 574 catch (Exception e) 575 { 576 getLogger().error("An error occurred while getting icon path for step id {}", stepId, e); 577 } 578 579 return infos; 580 } 581 582 /** 583 * Get the workflow's steps available as unconditional result for actions 584 * @param workflowName the current workflow name 585 * @param actionId id of current action if exist, can be null 586 * @param isInitialState true if current selected state is the initial state 587 * @return a map of the workflow steps 588 */ 589 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 590 public Map<String, Object> getStatesToJson(String workflowName, Integer actionId, Boolean isInitialState) 591 { 592 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 593 _workflowRightHelper.checkReadRight(workflowDescriptor); 594 List<Map<String, Object>> states = new ArrayList<>(); 595 List<Integer> conditionalStepIds = new ArrayList<>(); 596 List<StepDescriptor> steps = workflowDescriptor.getSteps(); 597 if (actionId != null) 598 { 599 ActionDescriptor action = workflowDescriptor.getAction(actionId); 600 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 601 for (ConditionalResultDescriptor conditionalResult : conditionalResults) 602 { 603 conditionalStepIds.add(conditionalResult.getStep()); 604 } 605 } 606 for (StepDescriptor step : steps) 607 { 608 int stepId = step.getId(); 609 if (!conditionalStepIds.contains(stepId)) 610 { 611 Map<String, Object> stateInfos = new HashMap<>(); 612 stateInfos.put("id", stepId); 613 stateInfos.put("label", getStepLabelAsString(workflowDescriptor, stepId, true)); 614 states.add(stateInfos); 615 } 616 } 617 if (!isInitialState && !conditionalStepIds.contains(-1)) 618 { 619 //Same state 620 Map<String, Object> stateInfos = new HashMap<>(); 621 stateInfos.put("id", -1); 622 stateInfos.put("label", new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_RESULTS_SAME_STEP")); 623 states.add(stateInfos); 624 } 625 626 return Map.of("data", states); 627 } 628 629 630 /** 631 * Get the translated step label 632 * @param workflowDescriptor current workflow 633 * @param stepId id of current step 634 * @param showId true if id needs to be displayed in the label 635 * @return the step label as string 636 */ 637 public String getStepLabelAsString(WorkflowDescriptor workflowDescriptor, int stepId, boolean showId) 638 { 639 I18nizableText label = getStepLabel(workflowDescriptor, stepId); 640 return showId 641 ? _i18nHelper.translateKey(workflowDescriptor.getName(), label, DEFAULT_STEP_NAME) + " (" + stepId + ")" 642 : _i18nHelper.translateKey(workflowDescriptor.getName(), label, DEFAULT_STEP_NAME); 643 } 644 645 /** 646 * Get the step's icon path 647 * @param workflowDescriptor current worklfow 648 * @param stepId id of current step 649 * @return the icon path 650 */ 651 public String getStepIconPath(WorkflowDescriptor workflowDescriptor, int stepId) 652 { 653 I18nizableText label = getStepLabel(workflowDescriptor, stepId); 654 label = _workflowSessionHelper.getOldLabelKeyIfCloned(workflowDescriptor.getName(), label); 655 return _workflowHelper.getElementIconPath(label, __DEFAULT_STEP_ICON_PATH); 656 } 657 658 /** 659 * Get the step's icon path as base 64 for svg links 660 * @param workflowDescriptor current worklfow 661 * @param stepId id of current step 662 * @return the icon path as base 64 663 */ 664 public String getStepIconPathAsBase64(WorkflowDescriptor workflowDescriptor, int stepId) 665 { 666 I18nizableText label = getStepLabel(workflowDescriptor, stepId); 667 label = _workflowSessionHelper.getOldLabelKeyIfCloned(workflowDescriptor.getName(), label); 668 return _workflowHelper.getElementIconAsBase64(label, __DEFAULT_SVG_STEP_ICON_PATH); 669 } 670 671 /** 672 * Get the step i18n label 673 * @param workflowDescriptor current workflow 674 * @param stepId id of current step 675 * @return the i18n step label 676 */ 677 public I18nizableText getStepLabel(WorkflowDescriptor workflowDescriptor, int stepId) 678 { 679 switch (stepId) 680 { 681 case -1: 682 return new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_RESULTS_SAME_STEP"); 683 case 0: 684 return new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_INITIAL_STEP_NAME"); 685 default: 686 StepDescriptor step = workflowDescriptor.getStep(stepId); 687 return getStepLabel(step); 688 } 689 } 690 691 /** 692 * Get a list of actions outgoing from current step 693 * @param stepId id of current step 694 * @param workflow current workflow 695 * @return the list of outgoing actions 696 */ 697 public List<ActionDescriptor> getOutgoingActions(int stepId, WorkflowDescriptor workflow) 698 { 699 return stepId != 0 700 ? workflow.getStep(stepId).getActions() 701 : workflow.getInitialActions(); 702 } 703 704 /** 705 * Get a set of actions incoming to current step 706 * @param stepId id of current step 707 * @param workflow current workflow 708 * @return the set of outgoing actions 709 */ 710 @SuppressWarnings("unchecked") 711 public Set<ActionDescriptor> getIncomingActions(int stepId , WorkflowDescriptor workflow) 712 { 713 Set<ActionDescriptor> incomingActions = new HashSet<>(); 714 if (stepId != 0) 715 { 716 incomingActions.addAll(_getIncomingActionsFromList(stepId, workflow.getInitialActions())); 717 List<StepDescriptor> steps = workflow.getSteps(); 718 for (StepDescriptor otherSteps : steps) 719 { 720 if (otherSteps.getId() != stepId) 721 { 722 List<ActionDescriptor> actions = otherSteps.getActions(); 723 incomingActions.addAll(_getIncomingActionsFromList(stepId, actions)); 724 } 725 } 726 } 727 return incomingActions; 728 } 729 730 /** 731 * Get a set of incoming actions if present in actions list 732 * @param stepId id of current step 733 * @param actions list of other step's actions 734 * @return a list containing other step's outgoing actions that are incoming to current step 735 */ 736 protected Set<ActionDescriptor> _getIncomingActionsFromList(int stepId, List<ActionDescriptor> actions) 737 { 738 Set<ActionDescriptor> incoming = new HashSet<>(); 739 for (ActionDescriptor action : actions) 740 { 741 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 742 if (unconditionalResult.getStep() == stepId) 743 { 744 incoming.add(action); 745 } 746 else 747 { 748 boolean leadToStep = false; 749 List<ResultDescriptor> conditionalResults = action.getConditionalResults(); 750 int indexResult = 0; 751 while (!leadToStep && indexResult < conditionalResults.size()) 752 { 753 if (conditionalResults.get(indexResult).getStep() == stepId) 754 { 755 incoming.add(action); 756 leadToStep = true; 757 } 758 indexResult++; 759 } 760 } 761 } 762 return incoming; 763 } 764 765 /** 766 * Get id of the first step having current action, INITIAL_STEP_ID if current action is an initial action 767 * @param stepId id of current step 768 * @param steps list of all the steps in current workflow 769 * @param actionId id of current action 770 * @return the id of the first found step having current action 771 */ 772 public String getFirstParentStepId(int stepId, List<StepDescriptor> steps, Integer actionId) 773 { 774 String firstParentStepId = ""; 775 int i = 0; 776 do 777 { 778 StepDescriptor otherStep = steps.get(i); 779 if (otherStep.getId() != stepId && otherStep.getAction(actionId) != null) 780 { 781 firstParentStepId = String.valueOf(otherStep.getId()); 782 } 783 i++; 784 } while (firstParentStepId.isEmpty() && i < steps.size()); 785 return firstParentStepId.isBlank() ? "0" : firstParentStepId; 786 } 787 788 private record StepWithIcon(Integer id, String label, String iconPath) { /* empty */ } 789}