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