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.Collections;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.cocoon.ProcessingException;
032import org.apache.commons.lang.StringUtils;
033import org.apache.commons.lang3.tuple.Pair;
034
035import org.ametys.core.ui.Callable;
036import org.ametys.plugins.workflow.EnhancedFunction;
037import org.ametys.plugins.workflow.EnhancedFunction.FunctionType;
038import org.ametys.plugins.workflow.EnhancedFunctionExtensionPoint;
039import org.ametys.plugins.workflow.ModelItemTypeExtensionPoint;
040import org.ametys.plugins.workflow.component.WorkflowArgument;
041import org.ametys.plugins.workflow.support.AvalonTypeResolver;
042import org.ametys.plugins.workflow.support.WorflowRightHelper;
043import org.ametys.plugins.workflow.support.WorkflowElementDefinitionHelper;
044import org.ametys.plugins.workflow.support.WorkflowHelper.WorkflowVisibility;
045import org.ametys.plugins.workflow.support.WorkflowSessionHelper;
046import org.ametys.runtime.i18n.I18nizableText;
047import org.ametys.runtime.model.DefinitionContext;
048import org.ametys.runtime.model.ElementDefinition;
049import org.ametys.runtime.model.SimpleViewItemGroup;
050import org.ametys.runtime.model.StaticEnumerator;
051import org.ametys.runtime.model.View;
052import org.ametys.runtime.model.ViewElement;
053import org.ametys.runtime.model.disableconditions.DefaultDisableConditions;
054import org.ametys.runtime.model.disableconditions.DisableCondition;
055import org.ametys.runtime.model.disableconditions.DisableCondition.OPERATOR;
056import org.ametys.runtime.model.disableconditions.DisableConditions;
057import org.ametys.runtime.model.disableconditions.DisableConditions.ASSOCIATION_TYPE;
058import org.ametys.runtime.model.disableconditions.VoidRelativeDisableCondition;
059import org.ametys.runtime.plugin.component.AbstractLogEnabled;
060
061import com.opensymphony.workflow.FunctionProvider;
062import com.opensymphony.workflow.TypeResolver;
063import com.opensymphony.workflow.WorkflowException;
064import com.opensymphony.workflow.loader.ActionDescriptor;
065import com.opensymphony.workflow.loader.DescriptorFactory;
066import com.opensymphony.workflow.loader.FunctionDescriptor;
067import com.opensymphony.workflow.loader.StepDescriptor;
068import com.opensymphony.workflow.loader.WorkflowDescriptor;
069
070/**
071 * DAO for workflow element's pre and pos functions
072 */
073public class WorkflowFunctionDAO extends AbstractLogEnabled implements Component, Serviceable
074{
075    /** Extension point for workflow arguments data type */
076    protected static ModelItemTypeExtensionPoint _workflowArgumentDataTypeExtensionPoint;
077    
078    private static final String __FUNCTION_DEFAULT_TYPE = "avalon";
079    private static final String __ATTRIBUTE_FUNCTIONS_LIST = "functions-list";
080    private static final String __ATTRIBUTE_FUNCTION_TYPES = "function-types";
081    
082    
083    /** The workflow session helper */
084    protected WorkflowSessionHelper _workflowSessionHelper;
085    
086    /** The workflow right helper */
087    protected WorflowRightHelper _workflowRightHelper;
088    
089    /** The workflow step DAO */
090    protected WorkflowStepDAO _workflowStepDAO;
091    
092    /** The workflow transition DAO */
093    protected WorkflowTransitionDAO _workflowTransitionDAO;
094    
095    /** The service manager */
096    protected ServiceManager _manager;
097
098    /** Extension point for EnhancedFunctions */
099    protected EnhancedFunctionExtensionPoint _enhancedFunctionExtensionPoint;
100    
101    public void service(ServiceManager smanager) throws ServiceException
102    {
103        _workflowSessionHelper = (WorkflowSessionHelper) smanager.lookup(WorkflowSessionHelper.ROLE);
104        _workflowRightHelper = (WorflowRightHelper) smanager.lookup(WorflowRightHelper.ROLE);
105        _workflowStepDAO = (WorkflowStepDAO) smanager.lookup(WorkflowStepDAO.ROLE);
106        _workflowTransitionDAO = (WorkflowTransitionDAO) smanager.lookup(WorkflowTransitionDAO.ROLE);
107        _enhancedFunctionExtensionPoint = (EnhancedFunctionExtensionPoint) smanager.lookup(EnhancedFunctionExtensionPoint.ROLE);
108        _workflowArgumentDataTypeExtensionPoint = (ModelItemTypeExtensionPoint) smanager.lookup(ModelItemTypeExtensionPoint.ROLE_WORKFLOW);
109        _manager = smanager;
110    }
111    
112    /**
113     * Get the function's parameters as fields to configure edition form panel
114     * @return the parameters field as Json readable map
115     * @throws ProcessingException exception while saxing view to json
116     */
117    @Callable(rights = {"Workflow_Right_Edit", "Workflow_Right_Edit_User"})
118    public Map<String, Object> getFunctionsModel() throws ProcessingException 
119    {
120        Map<String, Object> response = new HashMap<>();
121        
122        View view = new View();
123        SimpleViewItemGroup fieldset = new SimpleViewItemGroup();
124        fieldset.setName("functions");
125        
126        Set<Pair<String, EnhancedFunction>> enhancedFunctions = _enhancedFunctionExtensionPoint.getAllFunctions()
127                .stream()
128                .filter(this::_hasFunctionRight)
129                .collect(Collectors.toSet());
130        ElementDefinition<String> functionsList = _getFunctionListModelItem(enhancedFunctions);
131        List<ElementDefinition> argumentModelItems = _getArgumentsAndTypeModelItems(enhancedFunctions);
132        
133        ViewElement functionListView = new ViewElement();
134        functionListView.setDefinition(functionsList);
135        fieldset.addViewItem(functionListView);
136        
137        for (ElementDefinition functionArgument : argumentModelItems)
138        {
139            ViewElement argumentView = new ViewElement();
140            argumentView.setDefinition(functionArgument);
141            fieldset.addViewItem(argumentView);
142        }
143        
144        view.addViewItem(fieldset);
145        
146        response.put("parameters", view.toJSON(DefinitionContext.newInstance().withEdition(true)));
147        
148        return response;
149    }
150    
151    private boolean _hasFunctionRight(Pair<String, EnhancedFunction> function)
152    {
153        List<WorkflowVisibility> functionVisibilities = function.getRight().getVisibilities();
154        if (functionVisibilities.contains(WorkflowVisibility.USER))
155        {
156            return _workflowRightHelper.hasEditUserRight();
157        }
158        else if (functionVisibilities.contains(WorkflowVisibility.SYSTEM))
159        {
160            return _workflowRightHelper.hasEditSystemRight();
161        }
162        return false;
163    }
164
165    /**
166     * Get a list of workflow arguments model items with disable conditions on non related function selected
167     * @param enhancedFunctions a list of Pair with id and enhanced function
168     * @return the list of model items
169     */
170    protected List<ElementDefinition> _getArgumentsAndTypeModelItems(Set<Pair<String, EnhancedFunction>> enhancedFunctions)
171    {
172        List<ElementDefinition> argumentModelItems = new ArrayList<>();
173        for (Pair<String, EnhancedFunction> functionPair : enhancedFunctions)
174        {
175            String functionId = functionPair.getLeft();
176            
177            DisableCondition disableCondition = new VoidRelativeDisableCondition(__ATTRIBUTE_FUNCTIONS_LIST, OPERATOR.NEQ, functionId); 
178            
179            EnhancedFunction function = functionPair.getRight();
180            if (function.getFunctionExecType().equals(FunctionType.BOTH))
181            {
182                argumentModelItems.add(_getFunctionTypeModelItem(functionId, disableCondition));
183            }
184            
185            for (WorkflowArgument arg : function.getArguments())
186            {
187                arg.setName(functionId + "-" + arg.getName());
188                DisableConditions disableConditions = arg.getDisableConditions();
189                if (disableConditions == null)
190                {
191                    disableConditions = new DefaultDisableConditions();
192                }
193                disableConditions.setAssociation(ASSOCIATION_TYPE.OR);
194                disableConditions.getConditions().add(disableCondition);
195                arg.setDisableConditions(disableConditions);
196                argumentModelItems.add(arg);
197            }
198        }
199        return argumentModelItems;
200    }
201    
202    /**
203     * Get the model item for the list of functions
204     * @param enhancedFunctions the list of enhanced functions
205     * @return an enum of the functions as a model item 
206     */
207    protected ElementDefinition<String> _getFunctionListModelItem(Set<Pair<String, EnhancedFunction>> enhancedFunctions)
208    {
209        ElementDefinition<String> functionsList = WorkflowElementDefinitionHelper.getElementDefinition(
210            __ATTRIBUTE_FUNCTIONS_LIST,
211            new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_ROLE"), 
212            new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_ROLE_DESC"),
213            true, 
214            false
215        );
216        StaticEnumerator<String> functionsStaticEnumerator = new StaticEnumerator<>();
217        for (Pair<String, EnhancedFunction> function : enhancedFunctions)
218        {
219            functionsStaticEnumerator.add(function.getRight().getLabel(), function.getLeft());
220        }
221        functionsList.setEnumerator(functionsStaticEnumerator);
222        
223        return functionsList;
224    }
225
226    /**
227     * Get the view item for the fonctions types
228     * @param functionId id of current function
229     * @param disableCondition the condition for disabling the field
230     * @return the view element
231     */
232    protected ElementDefinition<String> _getFunctionTypeModelItem(String functionId, DisableCondition disableCondition)
233    {
234        DisableConditions disableConditions = new DefaultDisableConditions();
235        disableConditions.getConditions().add(disableCondition);
236        
237        StaticEnumerator<String> functionTypeStaticEnum = new StaticEnumerator<>();
238        functionTypeStaticEnum.add(new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_PREFUNCTION_TYPE"), FunctionType.PRE.name());
239        functionTypeStaticEnum.add(new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_POSTFUNCTION_TYPE"), FunctionType.POST.name());
240        
241        ElementDefinition<String> functionTypes = WorkflowElementDefinitionHelper.getElementDefinition(
242                __ATTRIBUTE_FUNCTION_TYPES + "-" + functionId,
243                new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_TYPES_LABEL"),
244                new I18nizableText("plugin.workflow", "PLUGINS_WORKFLOW_ADD_FUNCTION_DIALOG_TYPES_DESC"),
245                true,
246                false
247            );
248        functionTypes.setDisableConditions(disableConditions);
249        functionTypes.setEnumerator(functionTypeStaticEnum);
250        
251        return functionTypes;
252    }
253    
254    /**
255     * Get the function's parameters as a json view and their current values
256     * @param workflowName the workflow's unique name
257     * @param stepId the parent step's id
258     * @param actionId the parent action's id can be null
259     * @param type whether the function is a pre of post function
260     * @param id the function's id
261     * @param index the function's index in the pre/post function list
262     * @return map of the function's infos
263     */
264    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
265    public Map<String, Object> getFunctionParametersValues(String workflowName, Integer stepId, Integer actionId, String type, String id, Integer index) 
266    {
267        WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
268        _workflowRightHelper.checkEditRight(workflowDescriptor);
269        Map<String, String> args = new HashMap<>();
270        
271        FunctionDescriptor function = _getFunction(workflowDescriptor, stepId, actionId, type, index);
272        
273        //change arguments key to make them unique
274        Map<String, String> arguments = function.getArgs();
275        for (Entry<String, String> entry : arguments.entrySet())
276        {
277            String argName = entry.getKey().equals("id") ? __ATTRIBUTE_FUNCTIONS_LIST : id + "-" + entry.getKey();
278            args.put(argName, entry.getValue());
279        }
280        
281        Map<String, Object> results = new HashMap<>();
282        results.put("parametersValues", args);
283        return results;
284    }
285
286    private FunctionDescriptor _getFunction(WorkflowDescriptor workflowDescriptor, Integer stepId, Integer actionId, String type, Integer index)
287    {
288        List<FunctionDescriptor> functions = _getTypedFunctions(workflowDescriptor, stepId, actionId, type);
289        FunctionDescriptor function = functions.get(index);
290        return function;
291    }
292
293    /**
294     * Add a function to the workflow element
295     * @param workflowName the workflow's unique name
296     * @param stepId the parent step's id
297     * @param actionId the parent action's id can be null
298     * @param params Map of the function arguments
299     * @return map of the function's infos
300     */
301    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
302    public Map<String, Object> addFunction(String workflowName, Integer stepId, Integer actionId, Map<String, Object> params)
303    {
304        WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
305        _workflowRightHelper.checkEditRight(workflowDescriptor);
306        
307        String functionId = (String) params.get(__ATTRIBUTE_FUNCTIONS_LIST);
308        EnhancedFunction enhancedFunction = _enhancedFunctionExtensionPoint.getExtension(functionId);
309        
310        String functionType = enhancedFunction.getFunctionExecType().equals(FunctionType.BOTH) 
311                ? (String) params.get(__ATTRIBUTE_FUNCTION_TYPES + "-" + functionId) 
312                : enhancedFunction.getFunctionExecType().name();
313        
314        List<FunctionDescriptor> functions = _getTypedFunctions(workflowDescriptor, stepId, actionId, functionType);
315        Map<String, String> functionParams = _getFunctionParamsValuesAsString(enhancedFunction, functionId, params);
316        FunctionDescriptor newFunction = _createFunctionDescriptor(functionParams);
317        functions.add(newFunction);
318        
319        _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor);
320        
321        return _getFunctionInfos(workflowDescriptor, stepId, actionId, functionType, functionId, functions.size() - 1, true);
322    }
323
324    private FunctionDescriptor _createFunctionDescriptor(Map<String, String> functionParams)
325    {
326        DescriptorFactory factory = new DescriptorFactory();
327        FunctionDescriptor newFunction = factory.createFunctionDescriptor();
328        newFunction.setType(__FUNCTION_DEFAULT_TYPE);
329        newFunction.getArgs().putAll(functionParams);
330        
331        return newFunction;
332    }
333
334    /**
335     * Get the list of arguments as String, parse multiple arguments
336     * @param enhancedFunction the current function class
337     * @param functionId the function's id
338     * @param params List of function arguments with values
339     * @return the map of arguments formated for condition descriptor
340     */
341    protected Map<String, String> _getFunctionParamsValuesAsString(EnhancedFunction enhancedFunction, String functionId, Map<String, Object> params)
342    {
343        Map<String, String> functionParams = new HashMap<>();
344        functionParams.put("id", functionId);
345        List<WorkflowArgument> arguments = enhancedFunction.getArguments();
346        if (!arguments.isEmpty())
347        {
348            for (WorkflowArgument argument : arguments)
349            {
350                String paramKey = functionId + "-" + argument.getName();
351                String paramValue = argument.isMultiple()
352                        ?  _getListAsString(params, paramKey)
353                        : (String) params.get(paramKey);
354                if (StringUtils.isNotBlank(paramValue))
355                {
356                    functionParams.put(argument.getName(), paramValue);
357                }
358            }
359        }
360        return functionParams;
361    }
362
363    @SuppressWarnings("unchecked")
364    private String _getListAsString(Map<String, Object> params, String paramKey)
365    {
366        List<String> values = (List<String>) params.get(paramKey);
367        return values == null ? "" : String.join(",", values);
368    }
369
370    /**
371     * Get the list where belong a function can be either the prefunction list or the postfunctions
372     * @param workflowDescriptor the current workflow
373     * @param stepId id of current step
374     * @param actionId id of current action can be null if selection is a step
375     * @param functionType the type of function
376     * @return the typed list
377     */
378    protected List<FunctionDescriptor> _getTypedFunctions(WorkflowDescriptor workflowDescriptor, Integer stepId, Integer actionId, String functionType)
379    {
380        List<FunctionDescriptor> functions = new ArrayList<>();
381        if (actionId != null)
382        {
383            ActionDescriptor action = workflowDescriptor.getAction(actionId);
384            functions = functionType.equals(FunctionType.PRE.name()) ? action.getPreFunctions() : action.getPostFunctions();
385        }
386        else
387        {
388            StepDescriptor step = workflowDescriptor.getStep(stepId);
389            functions = functionType.equals(FunctionType.PRE.name()) ? step.getPreFunctions() : step.getPostFunctions();
390        }
391        return functions;
392    }
393    
394    /**
395     * Edit the function
396     * @param workflowName the workflow's unique name
397     * @param stepId the parent step's id
398     * @param actionId the parent action's id can be null
399     * @param oldType the saved type for this fonction
400     * @param params Map of the function arguments
401     * @param indexOfFunction the function's index in the pre/post function list
402     * @return map of the function's infos
403     */
404    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
405    public Map<String, Object> editFunction(String workflowName, Integer stepId, Integer actionId, String oldType, Map<String, Object> params, int indexOfFunction)
406    {
407        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
408        _workflowRightHelper.checkEditRight(workflow);
409    
410        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, oldType);
411        FunctionDescriptor function = functions.get(indexOfFunction);
412        
413        int index = indexOfFunction;
414        String functionId = (String) params.get(__ATTRIBUTE_FUNCTIONS_LIST);
415        EnhancedFunction enhancedFunction = _enhancedFunctionExtensionPoint.getExtension(functionId);
416        
417        String functionType = (String) params.get(__ATTRIBUTE_FUNCTION_TYPES + "-" + functionId);
418        String newType = StringUtils.isBlank(functionType) ? enhancedFunction.getFunctionExecType().name() : functionType;
419        
420        if (!oldType.equals(newType)) //Move function into the other typed list
421        {
422            functions.remove(function);
423            functions = _getTypedFunctions(workflow, stepId, actionId, newType);
424            functions.add(function);
425            index = functions.size() - 1;
426        }
427        
428        _updateFunctionArguments(function, functionId, enhancedFunction, params);
429        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
430        
431        return _getFunctionInfos(workflow, stepId, actionId, newType, functionId, index, _isLast(functions, indexOfFunction));
432    }
433    
434    private void _updateFunctionArguments(FunctionDescriptor function, String functionId, EnhancedFunction enhancedFunction, Map<String, Object> params)
435    {
436        Map<String, String> args = function.getArgs();
437        args.clear();
438        args.putAll(_getFunctionParamsValuesAsString(enhancedFunction, functionId, params));
439    }
440    
441    /**
442     * Remove the function from its parent 
443     * @param workflowName the workflow's unique name
444     * @param stepId the parent step's id
445     * @param actionId the parent action's id can be null
446     * @param functionType whether the function is a pre of post function
447     * @param id the function's id
448     * @param indexToRemove the function's index in the pre/post function list
449     * @return map of the function's infos
450     */
451    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
452    public Map<String, Object> deleteFunction(String workflowName, Integer stepId, Integer actionId, String functionType, String id, int indexToRemove)
453    {
454        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
455        _workflowRightHelper.checkEditRight(workflow);
456        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, functionType);
457        functions.remove(indexToRemove);
458        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
459        
460        return _getFunctionInfos(workflow, stepId, actionId, functionType, id, indexToRemove, _isLast(functions, indexToRemove));
461    }
462    
463    /**
464     * Swap the function with the one before it in the parent's pre/post function list
465     * @param workflowName the workflow's unique name
466     * @param stepId the parent step's id
467     * @param actionId the parent action's id can be null
468     * @param functionType whether the function is a pre of post function
469     * @param functionIndex the function's index in the pre/post function list
470     * @return map of the function's infos
471     */
472    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
473    public Map<String, Object> moveUp(String workflowName, Integer stepId, Integer actionId, String functionType, int functionIndex)
474    {
475        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
476        _workflowRightHelper.checkEditRight(workflow);
477        if (functionIndex == 0)
478        {
479            return Map.of("message", "top-queue");
480        }
481        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, functionType);
482        FunctionDescriptor functionToMove = functions.get(functionIndex);
483        Collections.swap(functions, functionIndex, functionIndex - 1);
484        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
485        
486        return _getFunctionInfos(workflow, stepId, actionId, functionType, (String) functionToMove.getArgs().get("id"), functionIndex - 1, false);
487    }
488    
489    /**
490     * Swap the function with the one after it in the parent's pre/post function list
491     * @param workflowName the workflow's unique name
492     * @param stepId the parent step's id
493     * @param actionId the parent action's id can be null
494     * @param functionType whether the function is a pre of post function
495     * @param functionIndex the function's index in the pre/post function list
496     * @return map of the function's infos
497     */
498    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
499    public Map<String, Object> moveDown(String workflowName, Integer stepId, Integer actionId, String functionType, int functionIndex)
500    {
501        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
502        _workflowRightHelper.checkEditRight(workflow);
503        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, functionType);
504        if (functionIndex == functions.size() - 1)
505        {
506            return Map.of("message", "bottom-queue");
507        }
508        FunctionDescriptor functionToMove = functions.get(functionIndex);
509        Collections.swap(functions, functionIndex, functionIndex + 1);
510        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
511        
512        return _getFunctionInfos(workflow, stepId, actionId, functionType, (String) functionToMove.getArgs().get("id"), functionIndex + 1, _isLast(functions, functionIndex + 1));
513    }
514    
515    /**
516     * Check if function can have bigger index in its parent's pre/post function list
517     * @param functions the current list of functions
518     * @param functionIndex the function's index in the pre/post function list
519     * @return true if the function can move down
520     */
521    protected boolean _isLast(List<FunctionDescriptor> functions, int functionIndex)
522    {
523        return functionIndex == functions.size() - 1;
524    }
525    
526    /**
527     * Get pre and postfunctions of current step
528     * @param workflowName the workflow unique name
529     * @param stepId id of the current step
530     * @return a list of the step's functions
531     */
532    @SuppressWarnings("unchecked")
533    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
534    public Map<String, Object> getStepFunctions(String workflowName, Integer stepId)
535    {
536        WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
537        List<Map<String, Object>> functions2json = new ArrayList<>();
538        if (_workflowRightHelper.canRead(workflowDescriptor))
539        {
540            StepDescriptor step = workflowDescriptor.getStep(stepId);
541            if (step != null)
542            {
543                functions2json.addAll(_functions2JSON(step.getPreFunctions(), FunctionType.PRE.name()));
544                functions2json.addAll(_functions2JSON(step.getPostFunctions(), FunctionType.POST.name()));
545            }
546        }
547        return Map.of("data", functions2json);
548    }
549    
550    /**
551     * Get pre and postfunctions of current action
552     * @param workflowName the workflow unique name
553     * @param actionId id of the current action
554     * @return a list of the action's functions
555     */
556    @SuppressWarnings("unchecked")
557    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
558    public Map<String, Object> getActionFunctions(String workflowName, Integer actionId)
559    {
560        WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
561        List<Map<String, Object>> functions2json = new ArrayList<>();
562        if (_workflowRightHelper.canRead(workflowDescriptor))
563        {
564            ActionDescriptor action = workflowDescriptor.getAction(actionId);
565            if (action != null)
566            {
567                functions2json.addAll(_functions2JSON(action.getPreFunctions(), FunctionType.PRE.name()));
568                functions2json.addAll(_functions2JSON(action.getPostFunctions(), FunctionType.POST.name()));
569            }
570        }
571        
572        return Map.of("data", functions2json);
573    }
574    
575    private List<Map<String, Object>> _functions2JSON(List<FunctionDescriptor> functions, String type)
576    {
577        List<Map<String, Object>> functions2json = new ArrayList<>();
578        for (int index = 0; index < functions.size(); index++)
579        {
580            functions2json.add(_function2JSON(functions.get(index), type, index, _isLast(functions, index)));
581        }
582        return functions2json;
583    }
584    
585    private Map<String, Object> _function2JSON(FunctionDescriptor workflowFunction, String type, int index, boolean isLast)
586    {
587        String id = (String) workflowFunction.getArgs().get("id");
588        Map<String, Object> functionInfos = new HashMap<>();
589        functionInfos.put("type", type);
590        functionInfos.put("id", id);
591        functionInfos.put("index", index);
592        functionInfos.put("isLast", isLast);
593        
594        try
595        {
596            TypeResolver typeResolver = new AvalonTypeResolver(_manager);
597            FunctionProvider function = typeResolver.getFunction(workflowFunction.getType(), workflowFunction.getArgs());
598            if (function instanceof EnhancedFunction enhancedFunction)
599            {
600                I18nizableText description = _getFunctionDescription(workflowFunction, enhancedFunction);
601                if (description != null)
602                {
603                    functionInfos.put("description", description);
604                }
605            }
606        }
607        catch (WorkflowException e)
608        {
609            getLogger().error("Function " + id + " couldn't be resolved", e);
610        }
611        return functionInfos;
612    }
613
614    private I18nizableText _getFunctionDescription(FunctionDescriptor workflowFunction, EnhancedFunction enhancedFunction)
615    {
616        List<WorkflowArgument> arguments = enhancedFunction.getArguments();
617        Map<String, String> values = new HashMap<>();
618        for (WorkflowArgument arg : arguments)
619        {
620            values.put(arg.getName(), (String) workflowFunction.getArgs().get(arg.getName()));
621        }
622        
623        I18nizableText description = enhancedFunction.getFullLabel(values);
624        return description;
625    }
626    
627    /**
628     * Get the function's description
629     * @param workflowName the workflow's unique name
630     * @param stepId the parent step's id
631     * @param actionId the parent action's id can be null
632     * @param type whether the function is a pre of post function
633     * @param id the enhanced function's id
634     * @param index the function's index in the pre/post function list
635     * @return the function's description
636     */
637    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
638    public I18nizableText getFunctionDescription(String workflowName, Integer stepId, Integer actionId, String type, String id, Integer index)
639    {
640        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
641        _workflowRightHelper.checkReadRight(workflow);
642        FunctionDescriptor function = _getFunction(workflow, stepId, actionId, type, index);
643        EnhancedFunction enhancedFunction = _enhancedFunctionExtensionPoint.getExtension(id);
644        return _getFunctionDescription(function, enhancedFunction);
645    }
646    
647    private Map<String, Object> _getFunctionInfos(WorkflowDescriptor workflow, Integer stepId, Integer actionId, String type, String id, int index, boolean isLast)
648    {
649        Map<String, Object> results = new HashMap<>();
650        results.put("workflowId", workflow.getName());
651        results.put("stepId", stepId);
652        results.put("stepLabel", _workflowStepDAO.getStepLabel(workflow, stepId));
653        if (actionId != null)
654        {
655            ActionDescriptor action = workflow.getAction(actionId);
656            results.put("actionId", actionId);
657            results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action));
658        }
659        results.put("type", type);
660        results.put("id", id);
661        results.put("index", index);
662        results.put("isLast", isLast);
663        return results;
664    }
665
666}