001/* 002 * Copyright 2010 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.cocoon; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.HashMap; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024 025import org.apache.avalon.framework.activity.Initializable; 026import org.apache.avalon.framework.parameters.Parameters; 027import org.apache.cocoon.acting.ServiceableAction; 028import org.apache.cocoon.environment.ObjectModelHelper; 029import org.apache.cocoon.environment.Redirector; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.environment.SourceResolver; 032import org.apache.commons.lang3.StringUtils; 033 034import org.ametys.core.authentication.AuthenticateAction; 035import org.ametys.core.cocoon.ActionResultGenerator; 036import org.ametys.plugins.workflow.AbstractWorkflowComponent; 037import org.ametys.plugins.workflow.AbstractWorkflowComponent.ConditionFailure; 038import org.ametys.plugins.workflow.component.CheckRightsCondition; 039import org.ametys.plugins.workflow.support.WorkflowProvider; 040import org.ametys.runtime.i18n.I18nizableText; 041import org.ametys.runtime.parameter.ValidationResult; 042 043import com.opensymphony.workflow.InvalidActionException; 044import com.opensymphony.workflow.InvalidInputException; 045import com.opensymphony.workflow.WorkflowException; 046 047/** 048 * Abstract action for managing a workflow instance. 049 * The following parameters are supported: 050 * <dl> 051 * <dt>actionId 052 * <dd>the id of the action to fire 053 * </dl> 054 */ 055public abstract class AbstractWorkflowAction extends ServiceableAction implements Initializable 056{ 057 /** Workflow provider */ 058 protected WorkflowProvider _workflowProvider; 059 060 @Override 061 public void initialize() throws Exception 062 { 063 _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE); 064 } 065 066 @Override 067 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 068 { 069 int actionId = _getActionId(objectModel, source, parameters); 070 Map inputs = _getInputs(redirector, objectModel, source, parameters); 071 072 try 073 { 074 Map result = _act(redirector, objectModel, source, parameters, actionId, inputs); 075 076 @SuppressWarnings("unchecked") 077 Map<String, Object> resultMap = (Map<String, Object>) inputs.get(AbstractWorkflowComponent.RESULT_MAP_KEY); 078 resultMap.put("success", true); 079 080 return result; 081 } 082 catch (InvalidActionException e) 083 { 084 return _processInvalidActionException(redirector, objectModel, source, parameters, actionId, inputs, e); 085 } 086 catch (WorkflowException e) 087 { 088 return _processWorkflowException(redirector, objectModel, source, parameters, actionId, e); 089 } 090 } 091 092 /** 093 * Provide the action id to use. 094 * Default implementation uses parameter <code>actionId</code>. 095 * @param objectModel the current object model. 096 * @param source the current source. 097 * @param parameters the current parameters. 098 * @return the action id. 099 * @throws Exception if an error occurs. 100 */ 101 protected int _getActionId(Map objectModel, String source, Parameters parameters) throws Exception 102 { 103 return parameters.getParameterAsInteger("actionId", Integer.MIN_VALUE); 104 } 105 106 /** 107 * Provide the inputs to use. 108 * Default implementation provide the redirector. 109 * @param redirector the redirector. 110 * @param objectModel the current object model. 111 * @param source the current source. 112 * @param parameters the current parameters. 113 * @return the inputs to use. 114 * @throws Exception if an error occurs. 115 */ 116 protected Map<String, Object> _getInputs(Redirector redirector, Map objectModel, String source, Parameters parameters) throws Exception 117 { 118 Map<String, Object> inputs = new HashMap<>(); 119 inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, objectModel.get(ObjectModelHelper.PARENT_CONTEXT)); 120 inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<>()); 121 122 Request request = ObjectModelHelper.getRequest(objectModel); 123 if (request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INTERNAL_ALLOWED) != null) 124 { 125 inputs.put(CheckRightsCondition.FORCE, true); 126 } 127 128 // Provide a map for providing data to the generator 129 Map<String, Object> result = new LinkedHashMap<>(); 130 request.setAttribute(ActionResultGenerator.MAP_REQUEST_ATTR, result); 131 inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, result); 132 133 return inputs; 134 } 135 136 /** 137 * Initialize or act on a workflow instance. 138 * @param redirector the redirector. 139 * @param objectModel the current object model. 140 * @param source the current source. 141 * @param parameters the current parameters. 142 * @param actionId the action id to use. 143 * @param inputs the inputs to use 144 * @return the action result. 145 * @throws InvalidInputException if the action id is not valid. 146 * @throws WorkflowException if the action failed. 147 */ 148 protected abstract Map _act(Redirector redirector, Map objectModel, String source, Parameters parameters, int actionId, Map inputs) throws InvalidInputException, WorkflowException; 149 150 /** 151 * Called when the current action is invalid. 152 * Default implementation throw an exception. 153 * @param redirector the redirector. 154 * @param objectModel the current object model. 155 * @param source the current source. 156 * @param parameters the current parameters. 157 * @param actionId the invalid action id. 158 * @param inputs the inputs 159 * @param e the invalid action exception. 160 * @return the action result. 161 * @throws Exception in order to stop the current pipeline. 162 */ 163 protected Map _processInvalidActionException(Redirector redirector, Map objectModel, String source, Parameters parameters, long actionId, Map inputs, InvalidActionException e) throws Exception 164 { 165 if (_getWorkflowErrors(inputs) != null) 166 { 167 // Errors was traited in result map 168 getLogger().error(String.format("Invalid action: '%d' for %s. %s", actionId, _getExceptionContext(objectModel, source, parameters), _getFailConditions(inputs)), e); 169 return Collections.EMPTY_MAP; 170 } 171 else 172 { 173 // The error was not traited 174 throw new Exception(String.format("Invalid action: '%d' for %s. %s", actionId, _getExceptionContext(objectModel, source, parameters), _getFailConditions(inputs)), e); 175 } 176 } 177 178 /** 179 * Get the fail conditions as a String 180 * @param inputs the inputs 181 * @return the fail conditions 182 */ 183 protected String _getFailConditions (Map inputs) 184 { 185 if (inputs.containsKey(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY)) 186 { 187 @SuppressWarnings("unchecked") 188 List<ConditionFailure> failConditions = (List<ConditionFailure>) inputs.get(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY); 189 if (failConditions.size() > 0) 190 { 191 return String.format("Fail conditions are: %s", StringUtils.join(failConditions.stream().map(ConditionFailure::text).toList(), " / ")); 192 } 193 } 194 195 return ""; 196 } 197 198 /** 199 * Get the workflow errors 200 * @param inputs The inputs 201 * @return The workflow errors or null if not found 202 */ 203 protected List<I18nizableText> _getWorkflowErrors (Map inputs) 204 { 205 @SuppressWarnings("unchecked") 206 Map<String, Object> result = (Map<String, Object>) inputs.get(AbstractWorkflowComponent.RESULT_MAP_KEY); 207 208 if (result.containsKey(AbstractWorkflowComponent.WORKFLOW_VALIDATION_KEY)) 209 { 210 return ((ValidationResult) result.get(AbstractWorkflowComponent.WORKFLOW_VALIDATION_KEY)).getErrors(); 211 } 212 213 return null; 214 } 215 216 /** 217 * Get the workflow warnings 218 * @param inputs The inputs 219 * @return The workflow warnings or null if not found 220 */ 221 @SuppressWarnings("unchecked") 222 protected List<I18nizableText> _getWorkflowWarns (Map inputs) 223 { 224 Map<String, Object> result = (Map<String, Object>) inputs.get(AbstractWorkflowComponent.RESULT_MAP_KEY); 225 226 if (result.containsKey(AbstractWorkflowComponent.WORKFLOW_VALIDATION_KEY)) 227 { 228 return ((ValidationResult) result.get(AbstractWorkflowComponent.WORKFLOW_VALIDATION_KEY)).getWarnings(); 229 } 230 231 return null; 232 } 233 234 /** 235 * Called when the current action has thrown a {@link WorkflowException}. 236 * Default implementation throw an exception. 237 * @param redirector the redirector. 238 * @param objectModel the current object model. 239 * @param source the current source. 240 * @param parameters the current parameters. 241 * @param actionId the invalid action id. 242 * @param e the workflow exception. 243 * @return the action result. 244 * @throws Exception in order to stop the current pipeline. 245 */ 246 protected Map _processWorkflowException(Redirector redirector, Map objectModel, String source, Parameters parameters, long actionId, WorkflowException e) throws Exception 247 { 248 throw new Exception(String.format("Unable to perform action: '%d' for %s", actionId, _getExceptionContext(objectModel, source, parameters)), e); 249 } 250 251 /** 252 * Provides the exception context for a more friendly error message. 253 * @param objectModel the current object model. 254 * @param source the current source. 255 * @param parameters the current parameters. 256 * @return the message describing the exception context. 257 */ 258 protected abstract String _getExceptionContext(Map objectModel, String source, Parameters parameters); 259}