001/*
002 *  Copyright 2014 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.cms.workflow;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.Map;
021
022import org.apache.avalon.framework.component.Component;
023import org.apache.avalon.framework.context.Context;
024import org.apache.avalon.framework.context.ContextException;
025import org.apache.avalon.framework.context.Contextualizable;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.cocoon.components.ContextHelper;
031import org.apache.cocoon.environment.ObjectModelHelper;
032import org.apache.cocoon.environment.Request;
033
034import org.ametys.cms.repository.Content;
035import org.ametys.cms.repository.WorkflowAwareContent;
036import org.ametys.core.user.CurrentUserProvider;
037import org.ametys.core.user.UserIdentity;
038import org.ametys.plugins.repository.AmetysRepositoryException;
039import org.ametys.plugins.workflow.AbstractWorkflowComponent;
040import org.ametys.plugins.workflow.support.WorkflowProvider;
041import org.ametys.plugins.workflow.support.WorkflowProvider.AmetysObjectWorkflow;
042
043import com.opensymphony.workflow.InvalidActionException;
044import com.opensymphony.workflow.WorkflowException;
045 
046/**
047 * A component to do workflow actions on Content
048 */
049public class ContentWorkflowHelper extends AbstractLogEnabled implements Serviceable, Contextualizable, Component
050{
051    /** The component role */
052    public static final String ROLE = ContentWorkflowHelper.class.getName();
053    
054    /** Component to get the current user */
055    protected CurrentUserProvider _userProvider;
056    
057    /** Workflow instance. */
058    protected WorkflowProvider _workflowProvider;
059
060    private Context _context;
061
062
063    @Override
064    public void contextualize(Context context) throws ContextException
065    {
066        _context = context;
067    }
068    
069    @Override
070    public void service(ServiceManager manager) throws ServiceException
071    {
072        _userProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
073        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
074    }
075    
076    /**
077     * Creates a content using the workflow (with the CreateContentFunction).
078     * @param workflowName The name of the workflow to create
079     * @param initialActionId The workflow action id that creates content
080     * @param contentName The new name
081     * @param contentTitle The new title
082     * @param contentTypes The new content types. Cannot be null. Cannot be empty.
083     * @param mixins The new mixins. Can be null. Can be empty.
084     * @param languageCode The language code of the new content (such as 'fr', 'en'...)
085     * @return The workflow result map. See the create content function used to get the new content. Can be under the key CreateContentFunction.CONTENT_KEY, and the id under the key "contentId"
086     * @throws WorkflowException If an error occurred while doing the action on the workflow
087     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
088     */
089    public Map<String, Object> createContent(String workflowName, int initialActionId, String contentName, String contentTitle, String[] contentTypes, String[] mixins, String languageCode) throws AmetysRepositoryException, WorkflowException
090    {
091        return createContent(workflowName, initialActionId, contentName, contentTitle, contentTypes, mixins, languageCode, null, null);
092    }
093    
094    /**
095     * Creates a multilingual content with a multilingual title using the workflow (with the CreateContentFunction).
096     * @param workflowName The name of the workflow to create
097     * @param initialActionId The workflow action id that creates content
098     * @param contentName The new name
099     * @param titleVariants The title's variants
100     * @param contentTypes The new content types. Cannot be null. Cannot be empty.
101     * @param mixins The new mixins. Can be null. Can be empty.
102     * @return The workflow result map. See the create content function used to get the new content. Can be under the key CreateContentFunction.CONTENT_KEY, and the id under the key "contentId"
103     * @throws WorkflowException If an error occurred while doing the action on the workflow
104     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
105     */
106    public Map<String, Object> createContent(String workflowName, int initialActionId, String contentName, Map<String, String> titleVariants, String[] contentTypes, String[] mixins) throws AmetysRepositoryException, WorkflowException
107    {
108        return createContent(workflowName, initialActionId, contentName, titleVariants, contentTypes, mixins, null, null);
109    }
110
111    /**
112     * Creates a content using the workflow (with the CreateContentFunction).
113     * @param workflowName The name of the workflow to create
114     * @param initialActionId The workflow action id that creates content
115     * @param contentName The new name
116     * @param contentTitle The new title
117     * @param contentTypes The new content types. Cannot be null. Cannot be empty.
118     * @param mixins The new mixins. Can be null. Can be empty.
119     * @param languageCode The language code of the new content (such as 'fr', 'en'...)
120     * @param parentContentId If the new content is a subcontent, the parent content identifier.
121     * @param parentContentMetadatapath If the new content is a subcontent, the path of the metadata where the new content will take place in its parent
122     * @return The workflow result map. See the create content function used to get the new content. Can be under the key CreateContentFunction.CONTENT_KEY, and the id under the key "contentId"
123     * @throws WorkflowException If an error occurred while doing the action on the workflow
124     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
125
126     */
127    public Map<String, Object> createContent(String workflowName, int initialActionId, String contentName, String contentTitle, String[] contentTypes, String[] mixins, String languageCode, String parentContentId, String parentContentMetadatapath) throws AmetysRepositoryException, WorkflowException
128    {
129        Map<String, Object> inputs = new HashMap<>();
130        return createContent(workflowName, initialActionId, contentName, contentTitle, contentTypes, mixins, languageCode, parentContentId, parentContentMetadatapath, inputs);
131    }
132    
133    /**
134     * Creates a multilingual content with a multilingual title using the workflow (with the CreateContentFunction).
135     * @param workflowName The name of the workflow to create
136     * @param initialActionId The workflow action id that creates content
137     * @param contentName The new name
138     * @param titleVariants The title's variants
139     * @param contentTypes The new content types. Cannot be null. Cannot be empty.
140     * @param mixins The new mixins. Can be null. Can be empty.
141     * @return The workflow result map. See the create content function used to get the new content. Can be under the key CreateContentFunction.CONTENT_KEY, and the id under the key "contentId"
142     * @param parentContentId If the new content is a subcontent, the parent content identifier.
143     * @param parentContentMetadatapath If the new content is a subcontent, the path of the metadata where the new content will take place in its parent
144     * @throws WorkflowException If an error occurred while doing the action on the workflow
145     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
146     */
147    public Map<String, Object> createContent(String workflowName, int initialActionId, String contentName, Map<String, String> titleVariants, String[] contentTypes, String[] mixins, String parentContentId, String parentContentMetadatapath) throws AmetysRepositoryException, WorkflowException
148    {
149        Map<String, Object> inputs = new HashMap<>();
150        return createContent(workflowName, initialActionId, contentName, titleVariants, contentTypes, mixins, parentContentId, parentContentMetadatapath, inputs);
151    }
152    
153    /**
154     * Creates a content using the workflow (with the CreateContentFunction).
155     * @param workflowName The name of the workflow to create
156     * @param initialActionId The workflow action id that creates content
157     * @param contentName The new name
158     * @param contentTitle The new title
159     * @param contentTypes The new content types. Cannot be null. Cannot be empty.
160     * @param mixins The new mixins. Can be null. Can be empty.
161     * @param languageCode The language code of the new content (such as 'fr', 'en'...)
162     * @param parentContentId If the new content is a subcontent, the parent content identifier.
163     * @param parentContentMetadatapath If the new content is a subcontent, the path of the metadata where the new content will take place in its parent
164     * @param inputs The parameters to transmit to the workflow functions. Cannot be null. 
165     * @return The workflow result map. See the create content function used to get the new content. Can be under the key CreateContentFunction.CONTENT_KEY, and the id under the key "contentId"
166     * @throws WorkflowException If an error occurred while doing the action on the workflow
167     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
168
169     */
170    public Map<String, Object> createContent(String workflowName, int initialActionId, String contentName, String contentTitle, String[] contentTypes, String[] mixins, String languageCode, String parentContentId, String parentContentMetadatapath, Map<String, Object> inputs) throws AmetysRepositoryException, WorkflowException 
171    {
172        _getCommonInputsForCreation(inputs, contentName, contentTypes, mixins, parentContentId, parentContentMetadatapath);
173        inputs.put(CreateContentFunction.CONTENT_TITLE_KEY, contentTitle);
174        inputs.put(CreateContentFunction.CONTENT_LANGUAGE_KEY, languageCode);
175        
176        return _doInitialize(workflowName, contentName, initialActionId, inputs);
177    }
178    
179    /**
180     * Creates a multilingual content with a multilingual title using the workflow (with the CreateContentFunction).
181     * @param workflowName The name of the workflow to create
182     * @param initialActionId The workflow action id that creates content
183     * @param contentName The new name
184     * @param titleVariants The title's variants
185     * @param contentTypes The new content types. Cannot be null. Cannot be empty.
186     * @param mixins The new mixins. Can be null. Can be empty.
187     * @return The workflow result map. See the create content function used to get the new content. Can be under the key CreateContentFunction.CONTENT_KEY, and the id under the key "contentId"
188     * @param parentContentId If the new content is a subcontent, the parent content identifier.
189     * @param parentContentMetadatapath If the new content is a subcontent, the path of the metadata where the new content will take place in its parent
190     * @param inputs The parameters to transmit to the workflow functions. Cannot be null. 
191     * @throws WorkflowException If an error occurred while doing the action on the workflow
192     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
193     */
194    public Map<String, Object> createContent(String workflowName, int initialActionId, String contentName, Map<String, String> titleVariants, String[] contentTypes, String[] mixins, String parentContentId, String parentContentMetadatapath, Map<String, Object> inputs) throws AmetysRepositoryException, WorkflowException
195    {
196        _getCommonInputsForCreation(inputs, contentName, contentTypes, mixins, parentContentId, parentContentMetadatapath);
197        inputs.put(CreateContentFunction.CONTENT_TITLE_VARIANTS_KEY, titleVariants);
198        
199        return _doInitialize(workflowName, contentName, initialActionId, inputs);
200    }
201    
202    @SuppressWarnings("unchecked")
203    private Map<String, Object> _doInitialize(String workflowName, String contentName, int initialActionId, Map<String, Object> inputs) throws WorkflowException
204    {
205        try
206        {
207            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow();
208            workflow.initialize(workflowName, initialActionId, inputs);
209            
210            return (Map<String, Object>) inputs.get(AbstractWorkflowComponent.RESULT_MAP_KEY);
211        }
212        catch (WorkflowException e)
213        {
214            getLogger().error("An error occured while creating workflow '" + workflowName + "' with action '" + initialActionId + "' to creates content '" + contentName + "'", e);
215            throw e;
216        }
217    }
218    
219    private void _getCommonInputsForCreation(Map<String, Object> inputs, String contentName, String[] contentTypes, String[] mixins, String parentContentId, String parentContentMetadatapath)
220    {
221        inputs.put(CreateContentFunction.CONTENT_NAME_KEY, contentName);
222        inputs.put(CreateContentFunction.CONTENT_TYPES_KEY, contentTypes);
223        inputs.put(CreateContentFunction.CONTENT_MIXINS_KEY, mixins);
224        inputs.put(CreateContentFunction.PARENT_CONTENT_ID_KEY, parentContentId);
225        inputs.put(CreateContentFunction.PARENT_CONTENT_METADATA_PATH_KEY, parentContentMetadatapath);
226        
227        Map<String, Object> results = new HashMap<>();
228        inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, results);
229        inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>());
230    }
231    
232    /**
233     * Get the available workflow actions for the content
234     * @param content The content to consider. Cannot be null.
235     * @return The array of actions ids that are available now
236     */
237    public int[] getAvailableActions(WorkflowAwareContent content)
238    {
239        Map<String, Object> inputs = new HashMap<>();
240        return getAvailableActions(content, inputs);
241    }
242    
243    /**
244     * Get the available workflow actions for the content
245     * @param content The content to consider. Cannot be null.
246     * @param inputs The parameters to transmit to the workflow functions. Cannot be null. 
247     * @return The array of actions ids that are available now
248     */
249    public int[] getAvailableActions(WorkflowAwareContent content, Map<String, Object> inputs)
250    {
251        AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content);
252        long wId = content.getWorkflowId();
253        
254        inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content);
255        inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String> ());
256        
257        return workflow.getAvailableActions(wId, inputs);
258    }
259    
260    /**
261     * Do a workflow action on a content.
262     * @param content The content to act on. Cannot be null.
263     * @param actionId The id of the workflow action to do
264     * @return The results of the functions
265     * @throws WorkflowException If an error occurred while doing the action on the workflow
266     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
267     */
268    public Map<String, Object> doAction(WorkflowAwareContent content, int actionId) throws AmetysRepositoryException, WorkflowException
269    {
270        Map<String, Object> inputs = new HashMap<>();
271        return doAction(content, actionId, inputs);
272    }
273    
274    /**
275     * Do a workflow action on a content.
276     * @param content The content to act on. Cannot be null.
277     * @param actionId The id of the workflow action to do
278     * @param inputs The parameters to transmit to the workflow functions. Cannot be null. The special key AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY will be filled with the parent context if null (this means that if your are in a request dispatched, you will automatically get the js parameters).
279     * @return The results of the functions
280     * @throws WorkflowException If an error occurred while doing the action on the workflow
281     * @throws AmetysRepositoryException If cannot get the workflow identifier of the content
282     */
283    public Map<String, Object> doAction(WorkflowAwareContent content, int actionId, Map<String, Object> inputs) throws AmetysRepositoryException, WorkflowException
284    {
285        if (getLogger().isInfoEnabled())
286        {
287            getLogger().info("User " + _getUser() + " try to perform action " + actionId + " on content " + content.getId());
288        }
289
290        Map<String, Object> results = new HashMap<>();
291        inputs.put(AbstractWorkflowComponent.RESULT_MAP_KEY, results);
292        inputs.put(AbstractContentWorkflowComponent.CONTENT_KEY, content);
293        inputs.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String>());
294        
295        if (inputs.get(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY) == null)
296        {
297            Map objectModel = ContextHelper.getObjectModel(_context);
298            @SuppressWarnings("unchecked")
299            Map<String, Object> jsParameters = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
300            inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, jsParameters);
301        }
302
303        try
304        {
305            Request request = ContextHelper.getRequest(_context);
306            request.setAttribute(Content.class.getName(), content);
307            
308            AmetysObjectWorkflow workflow = _workflowProvider.getAmetysObjectWorkflow(content);
309            workflow.doAction(content.getWorkflowId(), actionId, inputs);
310        }
311        catch (InvalidActionException e)
312        {
313            getLogger().error("An error occured while do workflow action '" + actionId + "' on content '" + content.getId() + "'", e);
314            throw e; 
315        }
316        
317        return results;
318    }
319
320    private UserIdentity _getUser()
321    {
322        return _userProvider.getUser();
323    }
324}