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; 022import java.util.Optional; 023 024import org.apache.avalon.framework.component.Component; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028 029import org.ametys.core.ui.Callable; 030import org.ametys.plugins.workflow.support.WorflowRightHelper; 031import org.ametys.plugins.workflow.support.WorkflowSessionHelper; 032import org.ametys.runtime.plugin.component.AbstractLogEnabled; 033 034import com.opensymphony.workflow.loader.AbstractDescriptor; 035import com.opensymphony.workflow.loader.ActionDescriptor; 036import com.opensymphony.workflow.loader.ConditionalResultDescriptor; 037import com.opensymphony.workflow.loader.ConditionsDescriptor; 038import com.opensymphony.workflow.loader.DescriptorFactory; 039import com.opensymphony.workflow.loader.WorkflowDescriptor; 040 041/** 042 * DAO for workflow results 043 */ 044public class WorkflowResultDAO extends AbstractLogEnabled implements Component, Serviceable 045{ 046 /** The component role */ 047 public static final String ROLE = WorkflowResultDAO.class.getName(); 048 049 /** The workflow session helper */ 050 protected WorkflowSessionHelper _workflowSessionHelper; 051 052 /** The workflow right helper */ 053 protected WorflowRightHelper _workflowRightHelper; 054 055 /** The workflow step DAO */ 056 protected WorkflowStepDAO _workflowStepDAO; 057 058 /** The workflow transition DAO */ 059 protected WorkflowTransitionDAO _workflowTransitionDAO; 060 061 public void service(ServiceManager manager) throws ServiceException 062 { 063 _workflowSessionHelper = (WorkflowSessionHelper) manager.lookup(WorkflowSessionHelper.ROLE); 064 _workflowRightHelper = (WorflowRightHelper) manager.lookup(WorflowRightHelper.ROLE); 065 _workflowStepDAO = (WorkflowStepDAO) manager.lookup(WorkflowStepDAO.ROLE); 066 _workflowTransitionDAO = (WorkflowTransitionDAO) manager.lookup(WorkflowTransitionDAO.ROLE); 067 } 068 069 /** 070 * Get a list of the action's conditional results in a json format 071 * @param workflowName the current workflow's unique name 072 * @param actionId the current action 073 * @param currentStepId in edition mode, id of the selected final step 074 * @return a list of available conditional results for this action 075 */ 076 @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION) 077 public Map<String, Object> getConditionalResultsToJson(String workflowName, Integer actionId, Integer currentStepId) 078 { 079 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 080 081 // Check user right 082 _workflowRightHelper.checkReadRight(workflowDescriptor); 083 084 ActionDescriptor action = workflowDescriptor.getAction(actionId); 085 List<Integer> usedSteps = new ArrayList<>(); 086 usedSteps.add(action.getUnconditionalResult().getStep()); 087 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 088 for (ConditionalResultDescriptor result : conditionalResults) 089 { 090 usedSteps.add(result.getStep()); 091 } 092 @SuppressWarnings("unchecked") 093 List<Map<String, Object>> workflowStatesToJson = (List<Map<String, Object>>) _workflowStepDAO.getStatesToJson(workflowName, actionId, workflowDescriptor.getInitialAction(actionId) != null).get("data"); 094 List<Map<String, Object>> states = new ArrayList<>(); 095 for (int i = 0; i < workflowStatesToJson.size(); i++) 096 { 097 Map<String, Object> state = workflowStatesToJson.get(i); 098 Integer id = (Integer) state.get("id"); 099 if (!usedSteps.contains(id)) 100 { 101 states.add(state); 102 } 103 } 104 if (currentStepId != null) 105 { 106 Map<String, Object> stateInfos = new HashMap<>(); 107 stateInfos.put("id", currentStepId); 108 boolean showId = currentStepId != -1; 109 stateInfos.put("label", _workflowStepDAO.getStepLabelAsString(workflowDescriptor, currentStepId, showId)); 110 states.add(stateInfos); 111 } 112 113 return Map.of("data", states); 114 } 115 116 /** 117 * Add a conditional result to action 118 * @param workflowName unique name of current workflow 119 * @param stepId id of the parent step of the selected action 120 * @param actionId id of selected action 121 * @param resultId id of step to add as conditional result 122 * @return map of result's infos 123 */ 124 @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION) 125 public Map<String, Object> addConditionalResult(String workflowName, Integer stepId, Integer actionId, Integer resultId) 126 { 127 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 128 129 // Check user right 130 _workflowRightHelper.checkEditRight(workflowDescriptor); 131 132 ActionDescriptor action = workflowDescriptor.getAction(actionId); 133 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 134 Optional<ConditionalResultDescriptor> sameFinalState = conditionalResults.stream().filter(cr -> cr.getStep() == resultId).findAny(); 135 if (sameFinalState.isPresent()) 136 { 137 return Map.of("message", "duplicate-state"); 138 } 139 140 DescriptorFactory factory = new DescriptorFactory(); 141 ConditionalResultDescriptor resultDescriptor = factory.createConditionalResultDescriptor(); 142 resultDescriptor.setStep(resultId); 143 conditionalResults.add(resultDescriptor); 144 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 145 146 return _getResultProperties(workflowDescriptor, action, stepId, resultId); 147 } 148 149 /** 150 * Edit a conditional result 151 * @param workflowName unique name of current workflow 152 * @param stepId id of the parent step of the selected action 153 * @param actionId id of selected action 154 * @param resultOldId former id of selected result 155 * @param resultNewId new id for selected result 156 * @return map of result's infos 157 */ 158 @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION) 159 public Map<String, Object> editConditionalResult(String workflowName, Integer stepId, Integer actionId, Integer resultOldId, Integer resultNewId) 160 { 161 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 162 163 // Check user right 164 _workflowRightHelper.checkEditRight(workflowDescriptor); 165 166 ActionDescriptor action = workflowDescriptor.getAction(actionId); 167 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 168 int indexOf = getIndexOfStepResult(conditionalResults, resultOldId); 169 if (indexOf != -1) 170 { 171 conditionalResults.get(indexOf).setStep(resultNewId); 172 } 173 174 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 175 176 return _getResultProperties(workflowDescriptor, action, stepId, resultNewId); 177 } 178 179 /** 180 * Delete a conditional result from action 181 * @param workflowName unique name of current workflow 182 * @param stepId id of the parent step of the selected action 183 * @param actionId id of selected action 184 * @param resultId id of the result to delete 185 * @return map of result's infos 186 */ 187 @Callable(rights = Callable.CHECKED_BY_IMPLEMENTATION) 188 public Map<String, Object> deleteConditionalResult(String workflowName, Integer stepId, Integer actionId, Integer resultId) 189 { 190 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 191 192 // Check user right 193 _workflowRightHelper.checkEditRight(workflowDescriptor); 194 195 ActionDescriptor action = workflowDescriptor.getAction(actionId); 196 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 197 int indexOf = getIndexOfStepResult(conditionalResults, resultId); 198 if (indexOf != -1) 199 { 200 conditionalResults.remove(indexOf); 201 } 202 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 203 204 return _getResultProperties(workflowDescriptor, action, stepId, resultId); 205 } 206 207 private Map<String, Object> _getResultProperties(WorkflowDescriptor workflowDescriptor, ActionDescriptor action, Integer stepId, Integer resultId) 208 { 209 Map<String, Object> results = new HashMap<>(); 210 results.put("nodeId", "step" + resultId); 211 results.put("nodeLabel", _workflowStepDAO.getStepLabel(workflowDescriptor, resultId)); 212 results.put("actionId", action.getId()); 213 results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action)); 214 results.put("stepId", stepId); 215 results.put("stepLabel", _workflowStepDAO.getStepLabel(workflowDescriptor, stepId)); 216 results.put("workflowId", workflowDescriptor.getName()); 217 results.put("isConditional", true); 218 return results; 219 } 220 221 /** 222 * Get children conditions of current node in result tree 223 * @param currentNode id of current node in tree 224 * @param action the selected action 225 * @param conditionalResults the action's conditional results 226 * @return a list of conditions, can be empty 227 */ 228 protected List<AbstractDescriptor> getChildrenResultConditions(String currentNode, ActionDescriptor action, List<ConditionalResultDescriptor> conditionalResults) 229 { 230 if (!conditionalResults.isEmpty()) 231 { 232 String[] path = getPath(currentNode); 233 int stepId = Integer.valueOf(path[0].substring(4)); 234 List<ConditionsDescriptor> resultConditions = getRootResultConditions(conditionalResults, stepId); 235 if (!resultConditions.isEmpty()) 236 { 237 ConditionsDescriptor rootConditionsDescriptor = resultConditions.get(0); 238 239 // The current node is root and it's a OR node, so display it ... 240 if (path.length == 1 && rootConditionsDescriptor.getType().equals(WorkflowConditionDAO.OR)) 241 { 242 return List.of(rootConditionsDescriptor); 243 } 244 // ... the current node is a AND node, display child conditions ... 245 else if (path.length == 1) 246 { 247 return rootConditionsDescriptor.getConditions(); 248 } 249 // ... the selected node is not a condition, so it has children 250 // we need to search the condition and get child condition of current node 251 else if (!path[path.length - 1].startsWith("condition")) 252 { 253 List<AbstractDescriptor> conditions = new ArrayList<>(); 254 conditions.add(rootConditionsDescriptor); 255 if (!conditions.isEmpty()) 256 { 257 // get conditions for current node 258 int i = 1; 259 do 260 { 261 String currentOrAndConditionId = path[i]; 262 int currentOrAndConditionIndex = (currentOrAndConditionId.startsWith("and")) 263 ? Integer.valueOf(currentOrAndConditionId.substring(3)) 264 : Integer.valueOf(currentOrAndConditionId.substring(2)); 265 266 ConditionsDescriptor currentOrAndCondition = (ConditionsDescriptor) conditions.get(currentOrAndConditionIndex); 267 conditions = currentOrAndCondition.getConditions(); 268 i++; 269 } 270 while (i < path.length); 271 272 return conditions; 273 } 274 } 275 } 276 } 277 278 return List.of(); 279 } 280 281 /** 282 * Get the path to the current node 283 * @param currentNodeId id of the current node, is like 'step1-(and|or)0-condition4' 284 * @return an array of the path (step1, and0, condition4) 285 */ 286 public String[] getPath(String currentNodeId) 287 { 288 String[] path = currentNodeId.split("-"); 289 if (path.length > 1 && path[1].equals("1")) 290 { 291 path[0] += "-1"; 292 String[] copyArray = new String[path.length - 1]; 293 copyArray[0] = path[0]; 294 System.arraycopy(path, 2, copyArray, 1, path.length - 2); 295 path = copyArray; 296 } 297 return path; 298 } 299 300 /** 301 * Get the root ConditionsDescriptor having current step as result 302 * this is the conditions descriptor that decide whether children conditions are linked by an AND or an OR operator 303 * @param conditionalResults list of current action's conditional results 304 * @param stepId id of current targeted step 305 * @return a list of the root ConditionsDescriptor if exist 306 */ 307 public List<ConditionsDescriptor> getRootResultConditions(List<ConditionalResultDescriptor> conditionalResults, int stepId) 308 { 309 return conditionalResults.stream() 310 .filter(r -> r.getStep() == stepId) 311 .map(r -> r.getConditions()) 312 .findFirst() 313 .orElse(List.of()); 314 } 315 316 /** 317 * Get the index of selected step result in the list of conditional results 318 * @param conditionalResults a list of conditional results 319 * @param stepResultId id of the result to find 320 * @return the step result's index 321 */ 322 public int getIndexOfStepResult(List<ConditionalResultDescriptor> conditionalResults, Integer stepResultId) 323 { 324 for (int i = 0; i < conditionalResults.size(); i++) 325 { 326 if (conditionalResults.get(i).getStep() == stepResultId) 327 { 328 return i; 329 } 330 } 331 return -1; 332 } 333}