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 @SuppressWarnings("cast") 353 List<StepDescriptor> steps = (List<StepDescriptor>) workflowDescriptor.getSteps(); 354 for (StepDescriptor step : steps) 355 { 356 Map<String, Object> infos = _step2JSON(workflowDescriptor, step.getId(), step.getActions().size() > 0, false, canWrite); 357 nodes.add(infos); 358 } 359 } 360 else if (currentNode.equals(INITIAL_STEP_ID)) 361 { 362 //initial actions 363 for (ActionDescriptor initialAction : initialActions) 364 { 365 nodes.add(_action2JSON(currentNode, initialAction, workflowDescriptor, canWrite)); 366 } 367 } 368 else 369 { 370 //regular actions 371 StepDescriptor step = workflowDescriptor.getStep(Integer.valueOf(currentNode.substring("step".length()))); 372 for (ActionDescriptor transition : (List<ActionDescriptor>) step.getActions()) 373 { 374 nodes.add(_action2JSON(currentNode, transition, workflowDescriptor, canWrite)); 375 } 376 } 377 } 378 379 return Map.of("steps", nodes); 380 } 381 382 /** 383 * Get the action infos for tree panel node 384 * @param stepId id of current step node 385 * @param action currently processed action 386 * @param workflowDescriptor current workflow 387 * @param canWrite true if current user has edition right on current workflow 388 * @return map of the action infos 389 */ 390 protected Map<String, Object> _action2JSON(String stepId, ActionDescriptor action, WorkflowDescriptor workflowDescriptor, boolean canWrite) 391 { 392 Set<StepWithIcon> finalSteps = _getActionFinalSteps(action, workflowDescriptor); 393 Map<Integer, Object> finalStepNames = finalSteps.stream().collect(Collectors.toMap(s -> s.id(), s -> s.label())); 394 Map<Integer, Object> finalStepIcons = finalSteps.stream().collect(Collectors.toMap(s -> s.id(), s -> s.iconPath())); 395 396 String iconPath = _workflowTransitionDAO.getActionIconPath(workflowDescriptor.getName(), action); 397 398 Map<String, Object> infos = new HashMap<>(); 399 400 infos.put("id", stepId + "-action" + action.getId()); 401 infos.put("elementId", action.getId()); 402 infos.put("smallIcon", iconPath); 403 infos.put("label", _workflowTransitionDAO.getActionLabel(workflowDescriptor.getName(), action)); 404 infos.put("elementType", "action"); 405 infos.put("hasChildren", false); 406 infos.put("targetedStepNames", finalStepNames); 407 infos.put("targetedStepIcons", finalStepIcons); 408 infos.put("canWrite", canWrite); 409 410 return infos; 411 } 412 413 /** 414 * Get the conditional and unconditional results of current action 415 * @param action the current action 416 * @param workflowDescriptor the current workflow 417 * @return a list of the final steps as (stepId, stepLabel, StepIconPath) 418 */ 419 protected Set<StepWithIcon> _getActionFinalSteps(ActionDescriptor action, WorkflowDescriptor workflowDescriptor) 420 { 421 Set<StepWithIcon> steps = new HashSet<>(); 422 for (StepDescriptor step : getOutgoingSteps(action, workflowDescriptor)) 423 { 424 int stepId = step.getId(); 425 steps.add(new StepWithIcon( 426 stepId, 427 getStepLabelAsString(workflowDescriptor, stepId, false), 428 getStepIconPath(workflowDescriptor, stepId)) 429 ); 430 } 431 432 return steps; 433 } 434 435 /** 436 * Get possible outgoing steps for action 437 * @param action the current action 438 * @param workflowDescriptor the current workflow 439 * @return a set of the outgoing steps 440 */ 441 public Set<StepDescriptor> getOutgoingSteps(ActionDescriptor action, WorkflowDescriptor workflowDescriptor) 442 { 443 Set<StepDescriptor> outgoingSteps = new HashSet<>(); 444 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 445 boolean hasSameStepTarget = false; 446 if (unconditionalResult.getStep() != -1) 447 { 448 StepDescriptor unconditionalStep = workflowDescriptor.getStep(unconditionalResult.getStep()); 449 outgoingSteps.add(unconditionalStep); 450 } 451 else 452 { 453 hasSameStepTarget = true; 454 } 455 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 456 for (ConditionalResultDescriptor result : conditionalResults) 457 { 458 StepDescriptor conditionalStep = workflowDescriptor.getStep(result.getStep()); 459 if (conditionalStep != null) 460 { 461 outgoingSteps.add(conditionalStep); 462 } 463 else 464 { 465 hasSameStepTarget = true; 466 } 467 } 468 if (hasSameStepTarget) 469 { 470 outgoingSteps.addAll(getIncomingSteps(action.getId(), workflowDescriptor)); 471 } 472 473 return outgoingSteps; 474 } 475 476 /** 477 * Get possible incoming steps for action 478 * @param actionId the current action's id 479 * @param workflowDescriptor the current workflow 480 * @return a set of the action's incoming steps 481 */ 482 public Set<StepDescriptor> getIncomingSteps(int actionId, WorkflowDescriptor workflowDescriptor) 483 { 484 Set<StepDescriptor> incomingSteps = new HashSet<>(); 485 List<StepDescriptor> steps = workflowDescriptor.getSteps(); 486 for (StepDescriptor step : steps) 487 { 488 if (step.getAction(actionId) != null) 489 { 490 incomingSteps.add(step); 491 } 492 } 493 return incomingSteps; 494 } 495 496 /** 497 * Get current action's final steps and associated conditions 498 * @param currentNode id of current node 499 * @param workflowName unique name of current workflow 500 * @param actionId id of current action 501 * @return a map of current node's children 502 */ 503 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 504 public Map<String, Object> getFinalSteps(String currentNode, String workflowName, Integer actionId) 505 { 506 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 507 List<Map<String, Object>> nodes = new ArrayList<>(); 508 if (_workflowRightHelper.canRead(workflowDescriptor)) 509 { 510 ActionDescriptor action = workflowDescriptor.getAction(actionId); 511 boolean canWrite = _workflowRightHelper.canWrite(workflowDescriptor); 512 513 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 514 515 if (currentNode.equals("root")) //get results(steps) nodes 516 { 517 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 518 int stepId = unconditionalResult.getStep(); 519 Map<String, Object> step2json = _step2JSON(workflowDescriptor, stepId, false, stepId != -1, canWrite); 520 step2json.put("isConditional", false); 521 nodes.add(step2json); 522 523 for (ConditionalResultDescriptor result : conditionalResults) 524 { 525 stepId = result.getStep(); 526 Map<String, Object> conditionalStep2Json = _step2JSON(workflowDescriptor, stepId, !result.getConditions().isEmpty(), stepId != -1, canWrite); 527 conditionalStep2Json.put("isConditional", true); 528 nodes.add(conditionalStep2Json); 529 } 530 } 531 else //get conditions nodes, 532 { 533 //conditions to display 534 List<AbstractDescriptor> conditions = _workflowResultDAO.getChildrenResultConditions(currentNode, action, conditionalResults); 535 if (!conditions.isEmpty()) 536 { 537 String[] path = _workflowResultDAO.getPath(currentNode); 538 int stepId = Integer.valueOf(path[0].substring(4)); 539 ConditionsDescriptor rootOperator = _workflowResultDAO.getRootResultConditions(conditionalResults, stepId).get(0); 540 boolean rootIsAND = !rootOperator.getType().equals(WorkflowConditionDAO.OR); 541 for (int i = 0; i < conditions.size(); i++) 542 { 543 nodes.add(_workflowConditionDAO.conditionToJSON(conditions.get(i), currentNode, i, rootIsAND)); 544 } 545 } 546 } 547 } 548 549 return Map.of("results", nodes); 550 } 551 552 /** 553 * Get step infos 554 * @param workflowDescriptor current workflow 555 * @param stepId id of current step 556 * @param hasChildren true if step has actions 557 * @param showId true if id needs to be displayed in the label 558 * @param canWrite true if current user has edition right on current workflow 559 * @return a map of the step infos 560 */ 561 protected Map<String, Object> _step2JSON(WorkflowDescriptor workflowDescriptor, int stepId, boolean hasChildren, boolean showId, boolean canWrite) 562 { 563 Map<String, Object> infos = new HashMap<>(); 564 565 infos.put("id", "step" + stepId); 566 infos.put("elementId", stepId); 567 infos.put("label", getStepLabelAsString(workflowDescriptor, stepId, showId)); 568 infos.put("elementType", "step"); 569 infos.put("hasChildren", hasChildren); 570 infos.put("canWrite", canWrite); 571 try 572 { 573 infos.put("smallIcon", getStepIconPath(workflowDescriptor, stepId)); 574 } 575 catch (Exception e) 576 { 577 getLogger().error("An error occurred while getting icon path for step id {}", stepId, e); 578 } 579 580 return infos; 581 } 582 583 /** 584 * Get the workflow's steps available as unconditional result for actions 585 * @param workflowName the current workflow name 586 * @param actionId id of current action if exist, can be null 587 * @param isInitialState true if current selected state is the initial state 588 * @return a map of the workflow steps 589 */ 590 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 591 public Map<String, Object> getStatesToJson(String workflowName, Integer actionId, Boolean isInitialState) 592 { 593 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 594 _workflowRightHelper.checkReadRight(workflowDescriptor); 595 List<Map<String, Object>> states = new ArrayList<>(); 596 List<Integer> conditionalStepIds = new ArrayList<>(); 597 List<StepDescriptor> steps = workflowDescriptor.getSteps(); 598 if (actionId != null) 599 { 600 ActionDescriptor action = workflowDescriptor.getAction(actionId); 601 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 602 for (ConditionalResultDescriptor conditionalResult : conditionalResults) 603 { 604 conditionalStepIds.add(conditionalResult.getStep()); 605 } 606 } 607 for (StepDescriptor step : steps) 608 { 609 int stepId = step.getId(); 610 if (!conditionalStepIds.contains(stepId)) 611 { 612 Map<String, Object> stateInfos = new HashMap<>(); 613 stateInfos.put("id", stepId); 614 stateInfos.put("label", getStepLabelAsString(workflowDescriptor, stepId, true)); 615 states.add(stateInfos); 616 } 617 } 618 if (!isInitialState && !conditionalStepIds.contains(-1)) 619 { 620 //Same state 621 Map<String, Object> stateInfos = new HashMap<>(); 622 stateInfos.put("id", -1); 623 stateInfos.put("label", new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_RESULTS_SAME_STEP")); 624 states.add(stateInfos); 625 } 626 627 return Map.of("data", states); 628 } 629 630 631 /** 632 * Get the translated step label 633 * @param workflowDescriptor current workflow 634 * @param stepId id of current step 635 * @param showId true if id needs to be displayed in the label 636 * @return the step label as string 637 */ 638 public String getStepLabelAsString(WorkflowDescriptor workflowDescriptor, int stepId, boolean showId) 639 { 640 I18nizableText label = getStepLabel(workflowDescriptor, stepId); 641 return showId 642 ? _i18nHelper.translateKey(workflowDescriptor.getName(), label, DEFAULT_STEP_NAME) + " (" + stepId + ")" 643 : _i18nHelper.translateKey(workflowDescriptor.getName(), label, DEFAULT_STEP_NAME); 644 } 645 646 /** 647 * Get the step's icon path 648 * @param workflowDescriptor current worklfow 649 * @param stepId id of current step 650 * @return the icon path 651 */ 652 public String getStepIconPath(WorkflowDescriptor workflowDescriptor, int stepId) 653 { 654 I18nizableText label = getStepLabel(workflowDescriptor, stepId); 655 label = _workflowSessionHelper.getOldLabelKeyIfCloned(workflowDescriptor.getName(), label); 656 return _workflowHelper.getElementIconPath(label, __DEFAULT_STEP_ICON_PATH); 657 } 658 659 /** 660 * Get the step's icon path as base 64 for svg links 661 * @param workflowDescriptor current worklfow 662 * @param stepId id of current step 663 * @return the icon path as base 64 664 */ 665 public String getStepIconPathAsBase64(WorkflowDescriptor workflowDescriptor, int stepId) 666 { 667 I18nizableText label = getStepLabel(workflowDescriptor, stepId); 668 label = _workflowSessionHelper.getOldLabelKeyIfCloned(workflowDescriptor.getName(), label); 669 return _workflowHelper.getElementIconAsBase64(label, __DEFAULT_SVG_STEP_ICON_PATH); 670 } 671 672 /** 673 * Get the step i18n label 674 * @param workflowDescriptor current workflow 675 * @param stepId id of current step 676 * @return the i18n step label 677 */ 678 public I18nizableText getStepLabel(WorkflowDescriptor workflowDescriptor, int stepId) 679 { 680 switch (stepId) 681 { 682 case -1: 683 return new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_RESULTS_SAME_STEP"); 684 case 0: 685 return new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_INITIAL_STEP_NAME"); 686 default: 687 StepDescriptor step = workflowDescriptor.getStep(stepId); 688 return getStepLabel(step); 689 } 690 } 691 692 /** 693 * Get a list of actions outgoing from current step 694 * @param stepId id of current step 695 * @param workflow current workflow 696 * @return the list of outgoing actions 697 */ 698 public List<ActionDescriptor> getOutgoingActions(int stepId, WorkflowDescriptor workflow) 699 { 700 return stepId != 0 701 ? workflow.getStep(stepId).getActions() 702 : workflow.getInitialActions(); 703 } 704 705 /** 706 * Get a set of actions incoming to current step 707 * @param stepId id of current step 708 * @param workflow current workflow 709 * @return the set of outgoing actions 710 */ 711 @SuppressWarnings("unchecked") 712 public Set<ActionDescriptor> getIncomingActions(int stepId , WorkflowDescriptor workflow) 713 { 714 Set<ActionDescriptor> incomingActions = new HashSet<>(); 715 if (stepId != 0) 716 { 717 incomingActions.addAll(_getIncomingActionsFromList(stepId, workflow.getInitialActions())); 718 List<StepDescriptor> steps = workflow.getSteps(); 719 for (StepDescriptor otherSteps : steps) 720 { 721 if (otherSteps.getId() != stepId) 722 { 723 List<ActionDescriptor> actions = otherSteps.getActions(); 724 incomingActions.addAll(_getIncomingActionsFromList(stepId, actions)); 725 } 726 } 727 } 728 return incomingActions; 729 } 730 731 /** 732 * Get a set of incoming actions if present in actions list 733 * @param stepId id of current step 734 * @param actions list of other step's actions 735 * @return a list containing other step's outgoing actions that are incoming to current step 736 */ 737 protected Set<ActionDescriptor> _getIncomingActionsFromList(int stepId, List<ActionDescriptor> actions) 738 { 739 Set<ActionDescriptor> incoming = new HashSet<>(); 740 for (ActionDescriptor action : actions) 741 { 742 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 743 if (unconditionalResult.getStep() == stepId) 744 { 745 incoming.add(action); 746 } 747 else 748 { 749 boolean leadToStep = false; 750 List<ResultDescriptor> conditionalResults = action.getConditionalResults(); 751 int indexResult = 0; 752 while (!leadToStep && indexResult < conditionalResults.size()) 753 { 754 if (conditionalResults.get(indexResult).getStep() == stepId) 755 { 756 incoming.add(action); 757 leadToStep = true; 758 } 759 indexResult++; 760 } 761 } 762 } 763 return incoming; 764 } 765 766 /** 767 * Get id of the first step having current action, INITIAL_STEP_ID if current action is an initial action 768 * @param stepId id of current step 769 * @param steps list of all the steps in current workflow 770 * @param actionId id of current action 771 * @return the id of the first found step having current action 772 */ 773 public String getFirstParentStepId(int stepId, List<StepDescriptor> steps, Integer actionId) 774 { 775 String firstParentStepId = ""; 776 int i = 0; 777 do 778 { 779 StepDescriptor otherStep = steps.get(i); 780 if (otherStep.getId() != stepId && otherStep.getAction(actionId) != null) 781 { 782 firstParentStepId = String.valueOf(otherStep.getId()); 783 } 784 i++; 785 } while (firstParentStepId.isEmpty() && i < steps.size()); 786 return firstParentStepId.isBlank() ? "0" : firstParentStepId; 787 } 788 789 private record StepWithIcon(Integer id, String label, String iconPath) { /* empty */ } 790}