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