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; 030import org.jsoup.internal.StringUtil; 031 032import org.ametys.core.ui.Callable; 033import org.ametys.core.util.I18nUtils; 034import org.ametys.plugins.workflow.support.WorkflowHelper; 035import org.ametys.runtime.i18n.I18nizableText; 036import org.ametys.runtime.plugin.component.AbstractLogEnabled; 037 038import com.opensymphony.workflow.loader.AbstractDescriptor; 039import com.opensymphony.workflow.loader.ActionDescriptor; 040import com.opensymphony.workflow.loader.ConditionalResultDescriptor; 041import com.opensymphony.workflow.loader.ConditionsDescriptor; 042import com.opensymphony.workflow.loader.ResultDescriptor; 043import com.opensymphony.workflow.loader.StepDescriptor; 044import com.opensymphony.workflow.loader.WorkflowDescriptor; 045 046/** 047 * DAO for workflow steps 048 */ 049public class WorkflowStepDAO extends AbstractLogEnabled implements Component, Serviceable 050{ 051 /** The component's role */ 052 public static final String ROLE = WorkflowStepDAO.class.getName(); 053 054 /** Id for initial step */ 055 public static final String INITIAL_STEP_ID = "step0"; 056 057 /** The default label for steps */ 058 public static final I18nizableText DEFAULT_STEP_NAME = new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_DEFAULT_STEP_LABEL"); 059 060 /** The default label for actions */ 061 public static final I18nizableText DEFAULT_ACTION_NAME = new I18nizableText("plugin.workflow", "PLUGIN_WORKFLOW_DEFAULT_ACTION_LABEL"); 062 063 /** Default path for svg step icons */ 064 protected static final String __DEFAULT_SVG_STEP_ICON_PATH = "plugin:cms://resources/img/history/workflow/step_0_16.png"; 065 /** Default path for svg action icons */ 066 protected static final String __DEFAULT_SVG_ACTION_ICON_PATH = "plugin:cms://resources/img/history/workflow/action_0_16.png"; 067 068 private static final String __DEFAULT_STEP_ICON_PATH = "/plugins/cms/resources/img/history/workflow/step_0_16.png"; 069 private static final String __DEFAULT_ACTION_ICON_PATH = "/plugins/cms/resources/img/history/workflow/action_0_16.png"; 070 071 /** The workflow helper */ 072 protected WorkflowHelper _workflowHelper; 073 074 /** The workflow condition DAO */ 075 protected WorkflowConditionDAO _workflowConditionDAO; 076 077 /** I18n Utils */ 078 protected I18nUtils _i18nUtils; 079 080 public void service(ServiceManager smanager) throws ServiceException 081 { 082 _workflowHelper = (WorkflowHelper) smanager.lookup(WorkflowHelper.ROLE); 083 _workflowConditionDAO = (WorkflowConditionDAO) smanager.lookup(WorkflowConditionDAO.ROLE); 084 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 085 } 086 087 /** 088 * Get the workflow editor tree's nodes 089 * @param currentNode id of the current node 090 * @param workflowName unique name of current workflow 091 * @return a map of the current node's children 092 */ 093 @Callable(right = "Workflow_Right_Read") 094 public Map<String, Object> getStepNodes(String currentNode, String workflowName) 095 { 096 List<Map<String, Object>> nodes = new ArrayList<>(); 097 WorkflowDescriptor workflowDescriptor = _workflowHelper.getWorkflowDescriptor(workflowName); 098 099 List<ActionDescriptor> initialActions = workflowDescriptor.getInitialActions(); 100 if (currentNode.equals("root")) 101 { 102 //initial step 103 Map<String, Object> infosInitialStep = _step2JSON(workflowDescriptor, 0, initialActions.size() > 0); 104 nodes.add(infosInitialStep); 105 106 //other steps 107 @SuppressWarnings("cast") 108 List<StepDescriptor> steps = (List<StepDescriptor>) workflowDescriptor.getSteps(); 109 for (StepDescriptor step : steps) 110 { 111 Map<String, Object> infos = _step2JSON(workflowDescriptor, step.getId(), step.getActions().size() > 0); 112 nodes.add(infos); 113 } 114 } 115 else if (currentNode.equals(INITIAL_STEP_ID)) 116 { 117 //initial actions 118 for (ActionDescriptor initialAction : initialActions) 119 { 120 nodes.add(_action2JSON(currentNode, initialAction, workflowDescriptor)); 121 } 122 } 123 else 124 { 125 //regular actions 126 StepDescriptor step = workflowDescriptor.getStep(Integer.valueOf(currentNode.substring("step".length()))); 127 for (ActionDescriptor transition : (List<ActionDescriptor>) step.getActions()) 128 { 129 nodes.add(_action2JSON(currentNode, transition, workflowDescriptor)); 130 } 131 } 132 133 return Map.of("steps", nodes); 134 } 135 136 /** 137 * Get the action infos for tree panel node 138 * @param stepId id of current step 139 * @param action currently processed action 140 * @param workflowDescriptor current workflow 141 * @return map of the action infos 142 */ 143 protected Map<String, Object> _action2JSON(String stepId, ActionDescriptor action, WorkflowDescriptor workflowDescriptor) 144 { 145 Set<StepWithIcon> finalSteps = _getActionFinalSteps(action, workflowDescriptor); 146 Map<Integer, Object> finalStepNames = finalSteps.stream().collect(Collectors.toMap(s -> s.id(), s -> s.label())); 147 Map<Integer, Object> finalStepIcons = finalSteps.stream().collect(Collectors.toMap(s -> s.id(), s -> s.iconPath())); 148 149 I18nizableText workflowActionName = new I18nizableText("application", action.getName()); 150 String iconPath = _workflowHelper.getElementIconPath(workflowActionName, __DEFAULT_ACTION_ICON_PATH); 151 152 Map<String, Object> infos = new HashMap<>(); 153 154 infos.put("id", stepId + "-action" + action.getId()); 155 infos.put("elementId", action.getId()); 156 infos.put("smallIcon", iconPath); 157 infos.put("label", getActionLabel(action)); 158 infos.put("elementType", "action"); 159 infos.put("hasChildren", false); 160 infos.put("targetedStepNames", finalStepNames); 161 infos.put("targetedStepIcons", finalStepIcons); 162 163 return infos; 164 } 165 166 /** 167 * Get the conditional and unconditional results of current action 168 * @param action the current action 169 * @param workflowDescriptor the current workflow 170 * @return a list of the final steps as (stepId, stepLabel, StepIconPath) 171 */ 172 protected Set<StepWithIcon> _getActionFinalSteps(ActionDescriptor action, WorkflowDescriptor workflowDescriptor) 173 { 174 Set<StepWithIcon> steps = new HashSet<>(); 175 for (StepDescriptor step : getOutgoingSteps(action, workflowDescriptor)) 176 { 177 int stepId = step.getId(); 178 steps.add(new StepWithIcon( 179 stepId, 180 getStepLabel(workflowDescriptor, stepId), 181 getStepIconPath(workflowDescriptor, stepId)) 182 ); 183 } 184 185 return steps; 186 } 187 188 /** 189 * Get possible outgoing steps for action 190 * @param action the current action 191 * @param workflowDescriptor the current workflow 192 * @return a set of the outgoing steps 193 */ 194 public Set<StepDescriptor> getOutgoingSteps(ActionDescriptor action, WorkflowDescriptor workflowDescriptor) 195 { 196 Set<StepDescriptor> outgoingSteps = new HashSet<>(); 197 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 198 if (unconditionalResult.getStep() != -1) 199 { 200 StepDescriptor unconditionalStep = workflowDescriptor.getStep(unconditionalResult.getStep()); 201 outgoingSteps.add(unconditionalStep); 202 } 203 else 204 { 205 outgoingSteps.addAll(getIncomingSteps(action.getId(), workflowDescriptor)); 206 } 207 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 208 for (ConditionalResultDescriptor result : conditionalResults) 209 { 210 StepDescriptor conditionalStep = workflowDescriptor.getStep(result.getStep()); 211 outgoingSteps.add(conditionalStep); 212 } 213 214 return outgoingSteps; 215 } 216 217 /** 218 * Get possible incoming steps for action 219 * @param actionId the current action's id 220 * @param workflowDescriptor the current workflow 221 * @return a set of the action's incoming steps 222 */ 223 public Set<StepDescriptor> getIncomingSteps(int actionId, WorkflowDescriptor workflowDescriptor) 224 { 225 Set<StepDescriptor> incomingSteps = new HashSet<>(); 226 List<StepDescriptor> steps = workflowDescriptor.getSteps(); 227 for (StepDescriptor step : steps) 228 { 229 if (step.getAction(actionId) != null) 230 { 231 incomingSteps.add(step); 232 } 233 } 234 return incomingSteps; 235 } 236 237 /** 238 * Get current action's final steps and associated conditions 239 * @param currentNode id of current node 240 * @param workflowName unique name of current workflow 241 * @param actionId id of current action 242 * @return a map of current node's children 243 */ 244 @Callable(right = "Workflow_Right_Read") 245 public Map<String, Object> getFinalSteps(String currentNode, String workflowName, String actionId) 246 { 247 WorkflowDescriptor workflowDescriptor = _workflowHelper.getWorkflowDescriptor(workflowName); 248 ActionDescriptor action = workflowDescriptor.getAction(Integer.valueOf(actionId)); 249 250 List<Map<String, Object>> nodes = new ArrayList<>(); 251 252 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 253 254 if (currentNode.equals("root")) //get results(steps) nodes 255 { 256 ResultDescriptor unconditionalResult = action.getUnconditionalResult(); 257 nodes.add(_step2JSON(workflowDescriptor, unconditionalResult.getStep(), false)); 258 259 for (ConditionalResultDescriptor result : conditionalResults) 260 { 261 StepDescriptor conditionalFinalStep = workflowDescriptor.getStep(result.getStep()); 262 nodes.add(_step2JSON(workflowDescriptor, conditionalFinalStep.getId(), true)); 263 } 264 } 265 else //get conditions nodes, 266 { 267 //FIXME this doesn't manage case where there are nested conditions because they don't exist yet 268 int stepId = _convertStepIdAsInteger(currentNode); 269 for (ConditionsDescriptor conditionWrapper: _getResultConditions(conditionalResults, stepId)) 270 { 271 List<AbstractDescriptor> conditions = conditionWrapper.getConditions(); 272 for (int i = 0; i < conditions.size(); i++) 273 { 274 nodes.add(_workflowConditionDAO.conditionToJSON(conditions.get(i), currentNode, i)); 275 } 276 } 277 } 278 279 return Map.of("results", nodes); 280 } 281 282 private Integer _convertStepIdAsInteger(String stepId) 283 { 284 return Integer.valueOf(stepId.substring(4)); 285 } 286 287 private List<ConditionsDescriptor> _getResultConditions(List<ConditionalResultDescriptor> conditionalResults, int stepId) 288 { 289 return conditionalResults.stream() 290 .filter(r -> r.getStep() == stepId) 291 .map(r -> r.getConditions()) 292 .findFirst() 293 .orElse(List.of()); 294 } 295 296 /** 297 * Get step infos 298 * @param workflowDescriptor current workflow 299 * @param stepId id of current step 300 * @param hasChildren true if step has actions 301 * @return a map of the step infos 302 */ 303 protected Map<String, Object> _step2JSON(WorkflowDescriptor workflowDescriptor, int stepId, boolean hasChildren) 304 { 305 Map<String, Object> infos = new HashMap<>(); 306 307 infos.put("id", "step" + stepId); 308 infos.put("elementId", stepId); 309 infos.put("label", getStepLabel(workflowDescriptor, stepId)); 310 infos.put("elementType", "step"); 311 infos.put("hasChildren", hasChildren); 312 try 313 { 314 infos.put("smallIcon", getStepIconPath(workflowDescriptor, stepId)); 315 } 316 catch (Exception e) 317 { 318 getLogger().error("An error occurred while getting icon path for step id {}", stepId, e); 319 } 320 321 return infos; 322 } 323 324 /** 325 * Get the translated step label 326 * @param workflowDescriptor current workflow 327 * @param stepId id of current step 328 * @return the step label as string 329 */ 330 public String getStepLabel(WorkflowDescriptor workflowDescriptor, int stepId) 331 { 332 I18nizableText label = _getStepLabel(workflowDescriptor, stepId); 333 return _translateKey(label, DEFAULT_STEP_NAME); 334 } 335 336 /** 337 * Get the step's icon path 338 * @param workflowDescriptor current worklfow 339 * @param stepId id of current step 340 * @return the icon path 341 */ 342 public String getStepIconPath(WorkflowDescriptor workflowDescriptor, int stepId) 343 { 344 I18nizableText label = _getStepLabel(workflowDescriptor, stepId); 345 return _workflowHelper.getElementIconPath(label, __DEFAULT_STEP_ICON_PATH); 346 } 347 348 /** 349 * Get the step's icon path as base 64 for svg links 350 * @param workflowDescriptor current worklfow 351 * @param stepId id of current step 352 * @return the icon path as base 64 353 */ 354 public String getStepIconPathAsBase64(WorkflowDescriptor workflowDescriptor, int stepId) 355 { 356 I18nizableText label = _getStepLabel(workflowDescriptor, stepId); 357 return _workflowHelper.getElementIconAsBase64(label, __DEFAULT_SVG_STEP_ICON_PATH); 358 } 359 360 private I18nizableText _getStepLabel(WorkflowDescriptor workflowDescriptor, int stepId) 361 { 362 switch (stepId) 363 { 364 case -1: 365 return new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_RESULTS_SAME_STEP"); 366 case 0: 367 return new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_INITIAL_STEP_NAME"); 368 default: 369 StepDescriptor step = workflowDescriptor.getStep(stepId); 370 return new I18nizableText("application", step.getName()); 371 } 372 } 373 374 /** 375 * Get the translated action label 376 * @param action current action 377 * @return the action label 378 */ 379 public String getActionLabel(ActionDescriptor action) 380 { 381 I18nizableText label = _getActionLabel(action); 382 return _translateKey(label, DEFAULT_ACTION_NAME); 383 } 384 385 /** 386 * Get the action's icon path 387 * @param action current action 388 * @return the icon's path 389 */ 390 public String getActionIconPath(ActionDescriptor action) 391 { 392 I18nizableText label = _getActionLabel(action); 393 return _workflowHelper.getElementIconPath(label, __DEFAULT_ACTION_ICON_PATH); 394 } 395 396 /** 397 * Get the action's icon path as base 64 for svg's links 398 * @param action current action 399 * @return the icon's path as base 64 400 */ 401 public String getActionIconPathAsBase64(ActionDescriptor action) 402 { 403 I18nizableText label = _getActionLabel(action); 404 return _workflowHelper.getElementIconAsBase64(label, __DEFAULT_SVG_ACTION_ICON_PATH); 405 } 406 407 private I18nizableText _getActionLabel(ActionDescriptor action) 408 { 409 return new I18nizableText("application", action.getName()); 410 } 411 412 /** 413 * Translate i18n label for workflow element, return a default name if translation is not found 414 * @param key an i18n key pointing to workflow element's label 415 * @return a translated label 416 */ 417 private String _translateKey(I18nizableText key, I18nizableText defaultKey) 418 { 419 String translate = _i18nUtils.translate(key); 420 return StringUtil.isBlank(translate) 421 ? defaultKey != null 422 ? _i18nUtils.translate(defaultKey) 423 : null 424 : translate; 425 } 426 427 private record StepWithIcon(Integer id, String label, String iconPath) { /* empty */ } 428 429}