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}