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