001/*
002 *  Copyright 2023 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.workflow.dao;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.component.Component;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.avalon.framework.service.Serviceable;
027
028import org.ametys.core.ui.Callable;
029import org.ametys.plugins.workflow.EnhancedCondition;
030import org.ametys.plugins.workflow.EnhancedCondition.ConditionArgument;
031import org.ametys.plugins.workflow.support.AvalonTypeResolver;
032import org.ametys.plugins.workflow.support.WorkflowHelper;
033import org.ametys.runtime.i18n.I18nizableText;
034import org.ametys.runtime.plugin.component.AbstractLogEnabled;
035
036import com.opensymphony.workflow.Condition;
037import com.opensymphony.workflow.TypeResolver;
038import com.opensymphony.workflow.WorkflowException;
039import com.opensymphony.workflow.loader.AbstractDescriptor;
040import com.opensymphony.workflow.loader.ActionDescriptor;
041import com.opensymphony.workflow.loader.ConditionDescriptor;
042import com.opensymphony.workflow.loader.ConditionsDescriptor;
043import com.opensymphony.workflow.loader.RestrictionDescriptor;
044import com.opensymphony.workflow.loader.WorkflowDescriptor;
045
046/**
047 * DAO for workflow conditions
048 */
049public class WorkflowConditionDAO extends AbstractLogEnabled implements Component, Serviceable
050{
051    /** The component role */ 
052    public static final String ROLE = WorkflowConditionDAO.class.getName();
053    
054    /** The "and" type of condition */
055    protected static final String __AND = "AND";
056    
057    /** The "or" type of condition */
058    protected static final String __OR = "OR";
059    
060    /** Key for "and" label in tree  */
061    protected static final String __ANDI18N = "PLUGIN_WORKFLOW_TRANSITION_CONDITIONS_TYPE_AND";
062    
063    /** Key for "or" label in tree  */
064    protected static final String __ORI18N = "PLUGIN_WORKFLOW_TRANSITION_CONDITIONS_TYPE_OR";
065    
066    /** The workflow helper */
067    protected WorkflowHelper _workflowHelper;
068    
069    /** The service manager */
070    protected ServiceManager _manager;
071
072    public void service(ServiceManager smanager) throws ServiceException
073    {
074        _workflowHelper = (WorkflowHelper) smanager.lookup(WorkflowHelper.ROLE);
075        _manager = smanager;
076    }
077    
078    /**
079     * Get the tree's condition nodes
080     * @param currentNode id of the current node 
081     * @param workflowName unique name of current workflow 
082     * @param actionId id of current action
083     * @return a map of current node's children
084     */
085    @Callable(right = "Workflow_Right_Read")
086    public Map<String, Object> getConditionNodes(String currentNode, String workflowName, String actionId) 
087    {
088        WorkflowDescriptor workflowDescriptor = _workflowHelper.getWorkflowDescriptor(workflowName);
089        ActionDescriptor action = workflowDescriptor.getAction(Integer.valueOf(actionId));
090        
091        List<Map<String, Object>> nodes = new ArrayList<>();
092        
093        List<AbstractDescriptor> conditions = _getConditions(currentNode, action);
094        for (int i = 0; i < conditions.size(); i++)
095        {
096            nodes.add(conditionToJSON(conditions.get(i), currentNode, i));
097        }
098        
099        return Map.of("conditions", nodes);
100    }
101    
102    /**
103     * Get conditions below current node
104     * @param currentNode id of the current node
105     * @param action current action
106     * @return a list of childnodes condition
107     */
108    protected List<AbstractDescriptor> _getConditions(String currentNode, ActionDescriptor action)
109    {
110        RestrictionDescriptor restriction = action.getRestriction();
111        if (restriction != null)
112        {
113            ConditionsDescriptor rootConditionsDescriptor = restriction.getConditionsDescriptor();
114
115            String[] path = currentNode.split("-");
116            // The current node is root and it's a OR node, so display it ...
117            if ("root".equals(currentNode) && rootConditionsDescriptor.getType().equals(__OR))
118            {
119                return List.of(rootConditionsDescriptor);
120            }
121            // ... the current node is a AND node, display child conditions ...
122            else if (path.length == 1)
123            {
124                return rootConditionsDescriptor.getConditions();
125            }
126            // ... the selected node is not a condition, so it has children
127            // we need to search the condition and get child condition of current node
128            else if (!path[path.length - 1].startsWith("condition")) 
129            {
130                List<AbstractDescriptor> conditions = rootConditionsDescriptor.getConditions();
131                // get conditions for current node
132                int i = 1;
133                do
134                {
135                    String currentOrAndConditionId = path[i];
136                    int currentOrAndConditionIndex = (currentOrAndConditionId.startsWith("and"))
137                            ? Integer.valueOf(currentOrAndConditionId.substring(3))
138                            : Integer.valueOf(currentOrAndConditionId.substring(2));
139                    
140                    ConditionsDescriptor currentOrAndCondition = (ConditionsDescriptor) conditions.get(currentOrAndConditionIndex);
141                    conditions = currentOrAndCondition.getConditions();
142                    i++;
143                }
144                while (i < path.length);
145
146                return conditions;
147            }
148        }
149        
150        return List.of();
151    }
152    
153    /**
154     * Get condition or condition types properties 
155     * @param condition current condition, can be ConditionsDescriptor or ConditionDescriptor 
156     * @param currentNodeId the id of the current node in the ConditionTreePanel
157     * @param index index of current condition in node's condition list
158     * @return a map of the condition infos
159     */
160    public Map<String, Object> conditionToJSON(AbstractDescriptor condition, String currentNodeId, int index) 
161    {
162        Map<String, Object> infosConditions = new HashMap<>();
163        
164        // if it's a 'and' or a 'or'
165        if (condition instanceof ConditionsDescriptor)
166        {
167            String type = ((ConditionsDescriptor) condition).getType();
168            if (type.equals(__AND))
169            {
170                String id = "root".equals(currentNodeId) ? "and0-and" + index : currentNodeId + "-and" + index;
171                infosConditions.put("id", id);
172                I18nizableText i18nLabel = new I18nizableText("plugin.workflow", __ANDI18N);
173                infosConditions.put("label", i18nLabel);
174            }
175            else
176            {
177                String id = "root".equals(currentNodeId) ? "or0" : currentNodeId + "-or" + index;
178                infosConditions.put("id", id);
179                I18nizableText i18nLabel = new I18nizableText("plugin.workflow", __ORI18N);
180                infosConditions.put("label", i18nLabel);
181            }
182            infosConditions.put("hasChildren", true);
183        }
184        else //it's a condition
185        {
186            String id = "root".equals(currentNodeId) ? "condition" + index : currentNodeId + "-condition" + index;
187            infosConditions.put("id", id);
188            infosConditions.put("label", _getConditionLabel((ConditionDescriptor) condition));
189            infosConditions.put("hasChildren", false);
190        }
191       
192        return infosConditions;
193    }
194    
195    /**
196     * Get condition's description or role 
197     * @param condition the current condition
198     * @return the condition description as I18nizableText if exist, or its role as String if not
199     */
200    protected Object _getConditionLabel(ConditionDescriptor condition) 
201    {
202        String role = (String) condition.getArgs().get("role");
203        TypeResolver typeResolver = new AvalonTypeResolver(_manager);
204        try
205        {
206            Condition function = typeResolver.getCondition(condition.getType(), condition.getArgs());
207            if (function instanceof EnhancedCondition)
208            {
209                List<ConditionArgument> arguments = ((EnhancedCondition) function).getArguments();
210                Map<String, String> values = new HashMap<>();
211                for (ConditionArgument arg : arguments)
212                {
213                    values.put(arg.name(), (String) condition.getArgs().get(arg.name()));
214                }
215                I18nizableText description = ((EnhancedCondition) function).getDescription(values);
216                
217                return description != null ? description : role;
218            }
219        }
220        catch (WorkflowException e)
221        {
222            getLogger().error("An error occured while resolving condition with role {}", role, e);
223        }
224        return role;
225    }
226}