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.Model;
050import org.ametys.runtime.model.SimpleViewItemGroup;
051import org.ametys.runtime.model.StaticEnumerator;
052import org.ametys.runtime.model.View;
053import org.ametys.runtime.model.ViewElement;
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.model.disableconditions.VoidRelativeDisableConditions;
060import org.ametys.runtime.plugin.component.AbstractLogEnabled;
061
062import com.opensymphony.workflow.FunctionProvider;
063import com.opensymphony.workflow.TypeResolver;
064import com.opensymphony.workflow.WorkflowException;
065import com.opensymphony.workflow.loader.ActionDescriptor;
066import com.opensymphony.workflow.loader.DescriptorFactory;
067import com.opensymphony.workflow.loader.FunctionDescriptor;
068import com.opensymphony.workflow.loader.StepDescriptor;
069import com.opensymphony.workflow.loader.WorkflowDescriptor;
070
071/**
072 * DAO for workflow element's pre and pos functions
073 */
074public class WorkflowFunctionDAO extends AbstractLogEnabled implements Component, Serviceable
075{
076    /** Extension point for workflow arguments data type */
077    protected static ModelItemTypeExtensionPoint _workflowArgumentDataTypeExtensionPoint;
078    
079    private static final String __FUNCTION_DEFAULT_TYPE = "avalon";
080    private static final String __ATTRIBUTE_FUNCTIONS_LIST = "functions-list";
081    private static final String __ATTRIBUTE_FUNCTION_TYPES = "function-types";
082    
083    
084    /** The workflow session helper */
085    protected WorkflowSessionHelper _workflowSessionHelper;
086    
087    /** The workflow right helper */
088    protected WorflowRightHelper _workflowRightHelper;
089    
090    /** The workflow step DAO */
091    protected WorkflowStepDAO _workflowStepDAO;
092    
093    /** The workflow transition DAO */
094    protected WorkflowTransitionDAO _workflowTransitionDAO;
095    
096    /** The service manager */
097    protected ServiceManager _manager;
098
099    /** Extension point for EnhancedFunctions */
100    protected EnhancedFunctionExtensionPoint _enhancedFunctionExtensionPoint;
101    
102    public void service(ServiceManager smanager) throws ServiceException
103    {
104        _workflowSessionHelper = (WorkflowSessionHelper) smanager.lookup(WorkflowSessionHelper.ROLE);
105        _workflowRightHelper = (WorflowRightHelper) smanager.lookup(WorflowRightHelper.ROLE);
106        _workflowStepDAO = (WorkflowStepDAO) smanager.lookup(WorkflowStepDAO.ROLE);
107        _workflowTransitionDAO = (WorkflowTransitionDAO) smanager.lookup(WorkflowTransitionDAO.ROLE);
108        _enhancedFunctionExtensionPoint = (EnhancedFunctionExtensionPoint) smanager.lookup(EnhancedFunctionExtensionPoint.ROLE);
109        _workflowArgumentDataTypeExtensionPoint = (ModelItemTypeExtensionPoint) smanager.lookup(ModelItemTypeExtensionPoint.ROLE_WORKFLOW);
110        _manager = smanager;
111    }
112    
113    /**
114     * Get the function's parameters as fields to configure edition form panel
115     * @return the parameters field as Json readable map
116     * @throws ProcessingException exception while saxing view to json
117     */
118    @Callable(rights = {"Workflow_Right_Edit", "Workflow_Right_Edit_User"})
119    public Map<String, Object> getFunctionsModel() throws ProcessingException 
120    {
121        Map<String, Object> response = new HashMap<>();
122        
123        View view = new View();
124        SimpleViewItemGroup fieldset = new SimpleViewItemGroup();
125        fieldset.setName("functions");
126        
127        Set<Pair<String, EnhancedFunction>> enhancedFunctions = _enhancedFunctionExtensionPoint.getAllFunctions()
128                .stream()
129                .filter(this::_hasFunctionRight)
130                .collect(Collectors.toSet());
131        
132        List<ElementDefinition> modelItems = new ArrayList<>();
133        modelItems.add(_getFunctionListModelItem(enhancedFunctions));
134        modelItems.addAll(_getArgumentsAndTypeModelItems(enhancedFunctions));
135        Model.of("functions", "worklow", modelItems.toArray(new ElementDefinition[modelItems.size()]));
136        
137        for (ElementDefinition functionArgument : modelItems)
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 VoidRelativeDisableConditions();
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 VoidRelativeDisableConditions();
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.CHECKED_BY_IMPLEMENTATION)
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        
269        // Check user right
270        _workflowRightHelper.checkEditRight(workflowDescriptor);
271        
272        Map<String, String> args = new HashMap<>();
273        
274        FunctionDescriptor function = _getFunction(workflowDescriptor, stepId, actionId, type, index);
275        
276        //change arguments key to make them unique
277        Map<String, String> arguments = function.getArgs();
278        for (Entry<String, String> entry : arguments.entrySet())
279        {
280            String argName = entry.getKey().equals("id") ? __ATTRIBUTE_FUNCTIONS_LIST : id + "-" + entry.getKey();
281            args.put(argName, entry.getValue());
282        }
283        
284        Map<String, Object> results = new HashMap<>();
285        results.put("parametersValues", args);
286        return results;
287    }
288
289    private FunctionDescriptor _getFunction(WorkflowDescriptor workflowDescriptor, Integer stepId, Integer actionId, String type, Integer index)
290    {
291        List<FunctionDescriptor> functions = _getTypedFunctions(workflowDescriptor, stepId, actionId, type);
292        FunctionDescriptor function = functions.get(index);
293        return function;
294    }
295
296    /**
297     * Add a function to the workflow element
298     * @param workflowName the workflow's unique name
299     * @param stepId the parent step's id
300     * @param actionId the parent action's id can be null
301     * @param params Map of the function arguments
302     * @return map of the function's infos
303     */
304    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
305    public Map<String, Object> addFunction(String workflowName, Integer stepId, Integer actionId, Map<String, Object> params)
306    {
307        WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
308        
309        // Check user right
310        _workflowRightHelper.checkEditRight(workflowDescriptor);
311        
312        String functionId = (String) params.get(__ATTRIBUTE_FUNCTIONS_LIST);
313        EnhancedFunction enhancedFunction = _enhancedFunctionExtensionPoint.getExtension(functionId);
314        
315        String functionType = enhancedFunction.getFunctionExecType().equals(FunctionType.BOTH) 
316                ? (String) params.get(__ATTRIBUTE_FUNCTION_TYPES + "-" + functionId) 
317                : enhancedFunction.getFunctionExecType().name();
318        
319        List<FunctionDescriptor> functions = _getTypedFunctions(workflowDescriptor, stepId, actionId, functionType);
320        Map<String, String> functionParams = _getFunctionParamsValuesAsString(enhancedFunction, functionId, params);
321        FunctionDescriptor newFunction = _createFunctionDescriptor(functionParams);
322        functions.add(newFunction);
323        
324        _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor);
325        
326        return _getFunctionInfos(workflowDescriptor, stepId, actionId, functionType, functionId, functions.size() - 1, true);
327    }
328
329    private FunctionDescriptor _createFunctionDescriptor(Map<String, String> functionParams)
330    {
331        DescriptorFactory factory = new DescriptorFactory();
332        FunctionDescriptor newFunction = factory.createFunctionDescriptor();
333        newFunction.setType(__FUNCTION_DEFAULT_TYPE);
334        newFunction.getArgs().putAll(functionParams);
335        
336        return newFunction;
337    }
338
339    /**
340     * Get the list of arguments as String, parse multiple arguments
341     * @param enhancedFunction the current function class
342     * @param functionId the function's id
343     * @param params List of function arguments with values
344     * @return the map of arguments formated for condition descriptor
345     */
346    protected Map<String, String> _getFunctionParamsValuesAsString(EnhancedFunction enhancedFunction, String functionId, Map<String, Object> params)
347    {
348        Map<String, String> functionParams = new HashMap<>();
349        functionParams.put("id", functionId);
350        List<WorkflowArgument> arguments = enhancedFunction.getArguments();
351        if (!arguments.isEmpty())
352        {
353            for (WorkflowArgument argument : arguments)
354            {
355                String paramKey = functionId + "-" + argument.getName();
356                String paramValue = argument.isMultiple()
357                        ?  _getListAsString(params, paramKey)
358                        : (String) params.get(paramKey);
359                if (StringUtils.isNotBlank(paramValue))
360                {
361                    functionParams.put(argument.getName(), paramValue);
362                }
363            }
364        }
365        return functionParams;
366    }
367
368    @SuppressWarnings("unchecked")
369    private String _getListAsString(Map<String, Object> params, String paramKey)
370    {
371        List<String> values = (List<String>) params.get(paramKey);
372        return values == null ? "" : String.join(",", values);
373    }
374
375    /**
376     * Get the list where belong a function can be either the prefunction list or the postfunctions
377     * @param workflowDescriptor the current workflow
378     * @param stepId id of current step
379     * @param actionId id of current action can be null if selection is a step
380     * @param functionType the type of function
381     * @return the typed list
382     */
383    protected List<FunctionDescriptor> _getTypedFunctions(WorkflowDescriptor workflowDescriptor, Integer stepId, Integer actionId, String functionType)
384    {
385        List<FunctionDescriptor> functions = new ArrayList<>();
386        if (actionId != null)
387        {
388            ActionDescriptor action = workflowDescriptor.getAction(actionId);
389            functions = functionType.equals(FunctionType.PRE.name()) ? action.getPreFunctions() : action.getPostFunctions();
390        }
391        else
392        {
393            StepDescriptor step = workflowDescriptor.getStep(stepId);
394            functions = functionType.equals(FunctionType.PRE.name()) ? step.getPreFunctions() : step.getPostFunctions();
395        }
396        return functions;
397    }
398    
399    /**
400     * Edit the function
401     * @param workflowName the workflow's unique name
402     * @param stepId the parent step's id
403     * @param actionId the parent action's id can be null
404     * @param oldType the saved type for this fonction
405     * @param params Map of the function arguments
406     * @param indexOfFunction the function's index in the pre/post function list
407     * @return map of the function's infos
408     */
409    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
410    public Map<String, Object> editFunction(String workflowName, Integer stepId, Integer actionId, String oldType, Map<String, Object> params, int indexOfFunction)
411    {
412        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
413        
414        // Check user right
415        _workflowRightHelper.checkEditRight(workflow);
416    
417        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, oldType);
418        FunctionDescriptor function = functions.get(indexOfFunction);
419        
420        int index = indexOfFunction;
421        String functionId = (String) params.get(__ATTRIBUTE_FUNCTIONS_LIST);
422        EnhancedFunction enhancedFunction = _enhancedFunctionExtensionPoint.getExtension(functionId);
423        
424        String functionType = (String) params.get(__ATTRIBUTE_FUNCTION_TYPES + "-" + functionId);
425        String newType = StringUtils.isBlank(functionType) ? enhancedFunction.getFunctionExecType().name() : functionType;
426        
427        if (!oldType.equals(newType)) //Move function into the other typed list
428        {
429            functions.remove(function);
430            functions = _getTypedFunctions(workflow, stepId, actionId, newType);
431            functions.add(function);
432            index = functions.size() - 1;
433        }
434        
435        _updateFunctionArguments(function, functionId, enhancedFunction, params);
436        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
437        
438        return _getFunctionInfos(workflow, stepId, actionId, newType, functionId, index, _isLast(functions, indexOfFunction));
439    }
440    
441    private void _updateFunctionArguments(FunctionDescriptor function, String functionId, EnhancedFunction enhancedFunction, Map<String, Object> params)
442    {
443        Map<String, String> args = function.getArgs();
444        args.clear();
445        args.putAll(_getFunctionParamsValuesAsString(enhancedFunction, functionId, params));
446    }
447    
448    /**
449     * Remove the function from its parent 
450     * @param workflowName the workflow's unique name
451     * @param stepId the parent step's id
452     * @param actionId the parent action's id can be null
453     * @param functionType whether the function is a pre of post function
454     * @param id the function's id
455     * @param indexToRemove the function's index in the pre/post function list
456     * @return map of the function's infos
457     */
458    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
459    public Map<String, Object> deleteFunction(String workflowName, Integer stepId, Integer actionId, String functionType, String id, int indexToRemove)
460    {
461        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
462        
463        // Check user right
464        _workflowRightHelper.checkEditRight(workflow);
465        
466        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, functionType);
467        functions.remove(indexToRemove);
468        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
469        
470        return _getFunctionInfos(workflow, stepId, actionId, functionType, id, indexToRemove, _isLast(functions, indexToRemove));
471    }
472    
473    /**
474     * Swap the function with the one before it in the parent's pre/post function list
475     * @param workflowName the workflow's unique name
476     * @param stepId the parent step's id
477     * @param actionId the parent action's id can be null
478     * @param functionType whether the function is a pre of post function
479     * @param functionIndex the function's index in the pre/post function list
480     * @return map of the function's infos
481     */
482    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
483    public Map<String, Object> moveUp(String workflowName, Integer stepId, Integer actionId, String functionType, int functionIndex)
484    {
485        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
486        
487        // Check user right
488        _workflowRightHelper.checkEditRight(workflow);
489        
490        if (functionIndex == 0)
491        {
492            return Map.of("message", "top-queue");
493        }
494        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, functionType);
495        FunctionDescriptor functionToMove = functions.get(functionIndex);
496        Collections.swap(functions, functionIndex, functionIndex - 1);
497        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
498        
499        return _getFunctionInfos(workflow, stepId, actionId, functionType, (String) functionToMove.getArgs().get("id"), functionIndex - 1, false);
500    }
501    
502    /**
503     * Swap the function with the one after it in the parent's pre/post function list
504     * @param workflowName the workflow's unique name
505     * @param stepId the parent step's id
506     * @param actionId the parent action's id can be null
507     * @param functionType whether the function is a pre of post function
508     * @param functionIndex the function's index in the pre/post function list
509     * @return map of the function's infos
510     */
511    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
512    public Map<String, Object> moveDown(String workflowName, Integer stepId, Integer actionId, String functionType, int functionIndex)
513    {
514        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true);
515        
516        // Check user right
517        _workflowRightHelper.checkEditRight(workflow);
518        
519        List<FunctionDescriptor> functions = _getTypedFunctions(workflow, stepId, actionId, functionType);
520        if (functionIndex == functions.size() - 1)
521        {
522            return Map.of("message", "bottom-queue");
523        }
524        FunctionDescriptor functionToMove = functions.get(functionIndex);
525        Collections.swap(functions, functionIndex, functionIndex + 1);
526        _workflowSessionHelper.updateWorkflowDescriptor(workflow);
527        
528        return _getFunctionInfos(workflow, stepId, actionId, functionType, (String) functionToMove.getArgs().get("id"), functionIndex + 1, _isLast(functions, functionIndex + 1));
529    }
530    
531    /**
532     * Check if function can have bigger index in its parent's pre/post function list
533     * @param functions the current list of functions
534     * @param functionIndex the function's index in the pre/post function list
535     * @return true if the function can move down
536     */
537    protected boolean _isLast(List<FunctionDescriptor> functions, int functionIndex)
538    {
539        return functionIndex == functions.size() - 1;
540    }
541    
542    /**
543     * Get pre and postfunctions of current step
544     * @param workflowName the workflow unique name
545     * @param stepId id of the current step
546     * @return a list of the step's functions
547     */
548    @SuppressWarnings("unchecked")
549    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
550    public Map<String, Object> getStepFunctions(String workflowName, Integer stepId)
551    {
552        WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
553        List<Map<String, Object>> functions2json = new ArrayList<>();
554        if (_workflowRightHelper.canRead(workflowDescriptor))
555        {
556            StepDescriptor step = workflowDescriptor.getStep(stepId);
557            if (step != null)
558            {
559                functions2json.addAll(_functions2JSON(step.getPreFunctions(), FunctionType.PRE.name()));
560                functions2json.addAll(_functions2JSON(step.getPostFunctions(), FunctionType.POST.name()));
561            }
562        }
563        return Map.of("data", functions2json);
564    }
565    
566    /**
567     * Get pre and postfunctions of current action
568     * @param workflowName the workflow unique name
569     * @param actionId id of the current action
570     * @return a list of the action's functions
571     */
572    @SuppressWarnings("unchecked")
573    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
574    public Map<String, Object> getActionFunctions(String workflowName, Integer actionId)
575    {
576        WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
577        List<Map<String, Object>> functions2json = new ArrayList<>();
578        if (_workflowRightHelper.canRead(workflowDescriptor))
579        {
580            ActionDescriptor action = workflowDescriptor.getAction(actionId);
581            if (action != null)
582            {
583                functions2json.addAll(_functions2JSON(action.getPreFunctions(), FunctionType.PRE.name()));
584                functions2json.addAll(_functions2JSON(action.getPostFunctions(), FunctionType.POST.name()));
585            }
586        }
587        
588        return Map.of("data", functions2json);
589    }
590    
591    private List<Map<String, Object>> _functions2JSON(List<FunctionDescriptor> functions, String type)
592    {
593        List<Map<String, Object>> functions2json = new ArrayList<>();
594        for (int index = 0; index < functions.size(); index++)
595        {
596            functions2json.add(_function2JSON(functions.get(index), type, index, _isLast(functions, index)));
597        }
598        return functions2json;
599    }
600    
601    private Map<String, Object> _function2JSON(FunctionDescriptor workflowFunction, String type, int index, boolean isLast)
602    {
603        String id = (String) workflowFunction.getArgs().get("id");
604        Map<String, Object> functionInfos = new HashMap<>();
605        functionInfos.put("type", type);
606        functionInfos.put("id", id);
607        functionInfos.put("index", index);
608        functionInfos.put("isLast", isLast);
609        
610        try
611        {
612            TypeResolver typeResolver = new AvalonTypeResolver(_manager);
613            FunctionProvider function = typeResolver.getFunction(workflowFunction.getType(), workflowFunction.getArgs());
614            if (function instanceof EnhancedFunction enhancedFunction)
615            {
616                I18nizableText description = _getFunctionDescription(workflowFunction, enhancedFunction);
617                if (description != null)
618                {
619                    functionInfos.put("description", description);
620                }
621            }
622        }
623        catch (WorkflowException e)
624        {
625            getLogger().error("Function " + id + " couldn't be resolved", e);
626        }
627        return functionInfos;
628    }
629
630    private I18nizableText _getFunctionDescription(FunctionDescriptor workflowFunction, EnhancedFunction enhancedFunction)
631    {
632        List<WorkflowArgument> arguments = enhancedFunction.getArguments();
633        Map<String, String> values = new HashMap<>();
634        for (WorkflowArgument arg : arguments)
635        {
636            values.put(arg.getName(), (String) workflowFunction.getArgs().get(arg.getName()));
637        }
638        
639        I18nizableText description = enhancedFunction.getFullLabel(values);
640        return description;
641    }
642    
643    /**
644     * Get the function's description
645     * @param workflowName the workflow's unique name
646     * @param stepId the parent step's id
647     * @param actionId the parent action's id can be null
648     * @param type whether the function is a pre of post function
649     * @param id the enhanced function's id
650     * @param index the function's index in the pre/post function list
651     * @return the function's description
652     */
653    @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION)
654    public I18nizableText getFunctionDescription(String workflowName, Integer stepId, Integer actionId, String type, String id, Integer index)
655    {
656        WorkflowDescriptor workflow = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false);
657        
658        // Check user right
659        _workflowRightHelper.checkReadRight(workflow);
660        
661        FunctionDescriptor function = _getFunction(workflow, stepId, actionId, type, index);
662        EnhancedFunction enhancedFunction = _enhancedFunctionExtensionPoint.getExtension(id);
663        return _getFunctionDescription(function, enhancedFunction);
664    }
665    
666    private Map<String, Object> _getFunctionInfos(WorkflowDescriptor workflow, Integer stepId, Integer actionId, String type, String id, int index, boolean isLast)
667    {
668        Map<String, Object> results = new HashMap<>();
669        results.put("workflowId", workflow.getName());
670        results.put("stepId", stepId);
671        results.put("stepLabel", _workflowStepDAO.getStepLabel(workflow, stepId));
672        if (actionId != null)
673        {
674            ActionDescriptor action = workflow.getAction(actionId);
675            results.put("actionId", actionId);
676            results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action));
677        }
678        results.put("type", type);
679        results.put("id", id);
680        results.put("index", index);
681        results.put("isLast", isLast);
682        return results;
683    }
684
685}