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.SKIP_BUILTIN_CHECK) 077 public Map<String, Object> getConditionalResultsToJson(String workflowName, Integer actionId, Integer currentStepId) 078 { 079 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, false); 080 _workflowRightHelper.checkReadRight(workflowDescriptor); 081 ActionDescriptor action = workflowDescriptor.getAction(actionId); 082 List<Integer> usedSteps = new ArrayList<>(); 083 usedSteps.add(action.getUnconditionalResult().getStep()); 084 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 085 for (ConditionalResultDescriptor result : conditionalResults) 086 { 087 usedSteps.add(result.getStep()); 088 } 089 @SuppressWarnings("unchecked") 090 List<Map<String, Object>> workflowStatesToJson = (List<Map<String, Object>>) _workflowStepDAO.getStatesToJson(workflowName, actionId, workflowDescriptor.getInitialAction(actionId) != null).get("data"); 091 List<Map<String, Object>> states = new ArrayList<>(); 092 for (int i = 0; i < workflowStatesToJson.size(); i++) 093 { 094 Map<String, Object> state = workflowStatesToJson.get(i); 095 Integer id = (Integer) state.get("id"); 096 if (!usedSteps.contains(id)) 097 { 098 states.add(state); 099 } 100 } 101 if (currentStepId != null) 102 { 103 Map<String, Object> stateInfos = new HashMap<>(); 104 stateInfos.put("id", currentStepId); 105 boolean showId = currentStepId != -1; 106 stateInfos.put("label", _workflowStepDAO.getStepLabelAsString(workflowDescriptor, currentStepId, showId)); 107 states.add(stateInfos); 108 } 109 110 return Map.of("data", states); 111 } 112 113 /** 114 * Add a conditional result to action 115 * @param workflowName unique name of current workflow 116 * @param stepId id of the parent step of the selected action 117 * @param actionId id of selected action 118 * @param resultId id of step to add as conditional result 119 * @return map of result's infos 120 */ 121 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 122 public Map<String, Object> addConditionalResult(String workflowName, Integer stepId, Integer actionId, Integer resultId) 123 { 124 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 125 _workflowRightHelper.checkEditRight(workflowDescriptor); 126 ActionDescriptor action = workflowDescriptor.getAction(actionId); 127 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 128 Optional<ConditionalResultDescriptor> sameFinalState = conditionalResults.stream().filter(cr -> cr.getStep() == resultId).findAny(); 129 if (sameFinalState.isPresent()) 130 { 131 return Map.of("message", "duplicate-state"); 132 } 133 134 DescriptorFactory factory = new DescriptorFactory(); 135 ConditionalResultDescriptor resultDescriptor = factory.createConditionalResultDescriptor(); 136 resultDescriptor.setStep(resultId); 137 conditionalResults.add(resultDescriptor); 138 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 139 140 return _getResultProperties(workflowDescriptor, action, stepId, resultId); 141 } 142 143 /** 144 * Edit a conditional result 145 * @param workflowName unique name of current workflow 146 * @param stepId id of the parent step of the selected action 147 * @param actionId id of selected action 148 * @param resultOldId former id of selected result 149 * @param resultNewId new id for selected result 150 * @return map of result's infos 151 */ 152 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 153 public Map<String, Object> editConditionalResult(String workflowName, Integer stepId, Integer actionId, Integer resultOldId, Integer resultNewId) 154 { 155 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 156 _workflowRightHelper.checkEditRight(workflowDescriptor); 157 ActionDescriptor action = workflowDescriptor.getAction(actionId); 158 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 159 int indexOf = getIndexOfStepResult(conditionalResults, resultOldId); 160 if (indexOf != -1) 161 { 162 conditionalResults.get(indexOf).setStep(resultNewId); 163 } 164 165 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 166 167 return _getResultProperties(workflowDescriptor, action, stepId, resultNewId); 168 } 169 170 /** 171 * Delete a conditional result from action 172 * @param workflowName unique name of current workflow 173 * @param stepId id of the parent step of the selected action 174 * @param actionId id of selected action 175 * @param resultId id of the result to delete 176 * @return map of result's infos 177 */ 178 @Callable(rights = Callable.SKIP_BUILTIN_CHECK) 179 public Map<String, Object> deleteConditionalResult(String workflowName, Integer stepId, Integer actionId, Integer resultId) 180 { 181 WorkflowDescriptor workflowDescriptor = _workflowSessionHelper.getWorkflowDescriptor(workflowName, true); 182 _workflowRightHelper.checkEditRight(workflowDescriptor); 183 ActionDescriptor action = workflowDescriptor.getAction(actionId); 184 List<ConditionalResultDescriptor> conditionalResults = action.getConditionalResults(); 185 int indexOf = getIndexOfStepResult(conditionalResults, resultId); 186 if (indexOf != -1) 187 { 188 conditionalResults.remove(indexOf); 189 } 190 _workflowSessionHelper.updateWorkflowDescriptor(workflowDescriptor); 191 192 return _getResultProperties(workflowDescriptor, action, stepId, resultId); 193 } 194 195 private Map<String, Object> _getResultProperties(WorkflowDescriptor workflowDescriptor, ActionDescriptor action, Integer stepId, Integer resultId) 196 { 197 Map<String, Object> results = new HashMap<>(); 198 results.put("nodeId", "step" + resultId); 199 results.put("nodeLabel", _workflowStepDAO.getStepLabel(workflowDescriptor, resultId)); 200 results.put("actionId", action.getId()); 201 results.put("actionLabel", _workflowTransitionDAO.getActionLabel(action)); 202 results.put("stepId", stepId); 203 results.put("stepLabel", _workflowStepDAO.getStepLabel(workflowDescriptor, stepId)); 204 results.put("workflowId", workflowDescriptor.getName()); 205 results.put("isConditional", true); 206 return results; 207 } 208 209 /** 210 * Get children conditions of current node in result tree 211 * @param currentNode id of current node in tree 212 * @param action the selected action 213 * @param conditionalResults the action's conditional results 214 * @return a list of conditions, can be empty 215 */ 216 protected List<AbstractDescriptor> getChildrenResultConditions(String currentNode, ActionDescriptor action, List<ConditionalResultDescriptor> conditionalResults) 217 { 218 if (!conditionalResults.isEmpty()) 219 { 220 String[] path = getPath(currentNode); 221 int stepId = Integer.valueOf(path[0].substring(4)); 222 List<ConditionsDescriptor> resultConditions = getRootResultConditions(conditionalResults, stepId); 223 if (!resultConditions.isEmpty()) 224 { 225 ConditionsDescriptor rootConditionsDescriptor = resultConditions.get(0); 226 227 // The current node is root and it's a OR node, so display it ... 228 if (path.length == 1 && rootConditionsDescriptor.getType().equals(WorkflowConditionDAO.OR)) 229 { 230 return List.of(rootConditionsDescriptor); 231 } 232 // ... the current node is a AND node, display child conditions ... 233 else if (path.length == 1) 234 { 235 return rootConditionsDescriptor.getConditions(); 236 } 237 // ... the selected node is not a condition, so it has children 238 // we need to search the condition and get child condition of current node 239 else if (!path[path.length - 1].startsWith("condition")) 240 { 241 List<AbstractDescriptor> conditions = new ArrayList<>(); 242 conditions.add(rootConditionsDescriptor); 243 if (!conditions.isEmpty()) 244 { 245 // get conditions for current node 246 int i = 1; 247 do 248 { 249 String currentOrAndConditionId = path[i]; 250 int currentOrAndConditionIndex = (currentOrAndConditionId.startsWith("and")) 251 ? Integer.valueOf(currentOrAndConditionId.substring(3)) 252 : Integer.valueOf(currentOrAndConditionId.substring(2)); 253 254 ConditionsDescriptor currentOrAndCondition = (ConditionsDescriptor) conditions.get(currentOrAndConditionIndex); 255 conditions = currentOrAndCondition.getConditions(); 256 i++; 257 } 258 while (i < path.length); 259 260 return conditions; 261 } 262 } 263 } 264 } 265 266 return List.of(); 267 } 268 269 /** 270 * Get the path to the current node 271 * @param currentNodeId id of the current node, is like 'step1-(and|or)0-condition4' 272 * @return an array of the path (step1, and0, condition4) 273 */ 274 public String[] getPath(String currentNodeId) 275 { 276 String[] path = currentNodeId.split("-"); 277 if (path.length > 1 && path[1].equals("1")) 278 { 279 path[0] += "-1"; 280 String[] copyArray = new String[path.length - 1]; 281 copyArray[0] = path[0]; 282 System.arraycopy(path, 2, copyArray, 1, path.length - 2); 283 path = copyArray; 284 } 285 return path; 286 } 287 288 /** 289 * Get the root ConditionsDescriptor having current step as result 290 * this is the conditions descriptor that decide whether children conditions are linked by an AND or an OR operator 291 * @param conditionalResults list of current action's conditional results 292 * @param stepId id of current targeted step 293 * @return a list of the root ConditionsDescriptor if exist 294 */ 295 public List<ConditionsDescriptor> getRootResultConditions(List<ConditionalResultDescriptor> conditionalResults, int stepId) 296 { 297 return conditionalResults.stream() 298 .filter(r -> r.getStep() == stepId) 299 .map(r -> r.getConditions()) 300 .findFirst() 301 .orElse(List.of()); 302 } 303 304 /** 305 * Get the index of selected step result in the list of conditional results 306 * @param conditionalResults a list of conditional results 307 * @param stepResultId id of the result to find 308 * @return the step result's index 309 */ 310 public int getIndexOfStepResult(List<ConditionalResultDescriptor> conditionalResults, Integer stepResultId) 311 { 312 for (int i = 0; i < conditionalResults.size(); i++) 313 { 314 if (conditionalResults.get(i).getStep() == stepResultId) 315 { 316 return i; 317 } 318 } 319 return -1; 320 } 321}