View Javadoc

1   /* ------------------------------------
2    * © Kaamelot - 2007
3    * ------------------------------------*/
4   package com.atlassian.jira.jelly.tag.issue;
5   
6   import java.util.Collection;
7   import java.util.HashMap;
8   import java.util.HashSet;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import org.apache.commons.jelly.JellyTagException;
15  import org.apache.commons.jelly.MissingAttributeException;
16  import org.apache.commons.jelly.XMLOutput;
17  import org.ofbiz.core.entity.GenericEntityException;
18  import org.ofbiz.core.entity.GenericValue;
19  
20  import com.atlassian.core.user.UserUtils;
21  import com.atlassian.jira.issue.Issue;
22  import com.atlassian.jira.issue.IssueFactory;
23  import com.atlassian.jira.issue.IssueManager;
24  import com.atlassian.jira.issue.MutableIssue;
25  import com.atlassian.jira.issue.customfields.impl.FieldValidationException;
26  import com.atlassian.jira.issue.fields.CustomField;
27  import com.atlassian.jira.issue.fields.FieldManager;
28  import com.atlassian.jira.issue.fields.OrderableField;
29  import com.atlassian.jira.issue.fields.screen.FieldScreen;
30  import com.atlassian.jira.jelly.JiraDynaBeanTagSupport;
31  import com.atlassian.jira.jelly.tag.JellyTagConstants;
32  import com.atlassian.jira.jelly.tag.JellyUtils;
33  import com.atlassian.jira.security.JiraAuthenticationContext;
34  import com.atlassian.jira.security.JiraAuthenticationContextImpl;
35  import com.atlassian.jira.util.EasyList;
36  import com.atlassian.jira.util.JiraUtils;
37  import com.atlassian.jira.workflow.JiraWorkflow;
38  import com.atlassian.jira.workflow.WorkflowActionsBean;
39  import com.atlassian.jira.workflow.WorkflowException;
40  import com.atlassian.jira.workflow.WorkflowManager;
41  import com.atlassian.jira.workflow.WorkflowTransitionUtil;
42  import com.atlassian.jira.workflow.WorkflowTransitionUtilAddOnImpl;
43  import com.opensymphony.user.EntityNotFoundException;
44  import com.opensymphony.user.User;
45  import com.opensymphony.util.TextUtils;
46  import com.opensymphony.workflow.loader.ActionDescriptor;
47  
48  public class TransitionWorkflowExtended extends JiraDynaBeanTagSupport implements CustomFieldValuesAwareTag
49  {
50     // Tag's attribtes
51     private static final String KEY_USER = "user";
52     private static final String KEY_ISSUE_KEY = "key";
53     private static final String KEY_WORKFLOW_ACTION = "workflowAction";
54     // Comment attributes
55     private static final String KEY_COMMENT = WorkflowTransitionUtil.FIELD_COMMENT;
56     private static final String KEY_COMMENT_LEVEL = WorkflowTransitionUtil.FIELD_COMMENT_LEVEL;
57  
58     public static final Set attributes = new HashSet();
59  
60     static
61     {
62         attributes.add(KEY_USER);
63         attributes.add(KEY_ISSUE_KEY);
64         attributes.add(KEY_WORKFLOW_ACTION);
65         attributes.add(KEY_COMMENT);
66         attributes.add(KEY_COMMENT_LEVEL);
67     }
68  
69     private final JiraAuthenticationContext authenticationContext;
70     private final IssueManager issueManager;
71     private final WorkflowManager workflowManager;
72     private final FieldManager fieldManager;
73     private final IssueFactory issueFactory;
74  
75     public TransitionWorkflowExtended(JiraAuthenticationContext authenticationContext, IssueManager issueManager, WorkflowManager workflowManager, FieldManager fieldManager, IssueFactory issueFactory)
76     {
77         this.authenticationContext = authenticationContext;
78         this.issueManager = issueManager;
79         this.workflowManager = workflowManager;
80         this.fieldManager = fieldManager;
81         this.issueFactory = issueFactory;
82     }
83  
84     public void doTag(XMLOutput xmlOutput) throws MissingAttributeException, JellyTagException
85     {
86         if (getBody() != null)
87         {
88             getBody().run(getContext(), xmlOutput);
89         }
90  
91         validateAttributes();
92  
93         User user = getUser();
94  
95         // If we have the properties we need to check if we can identify the workflow transition
96         MutableIssue issue = getIssue();
97  
98         int actionId = getActionId(issue).getId();
99  
100        Map params = getTransitionParameters(issue);
101 
102        // Remember the current user
103        User previousUser = authenticationContext.getUser();
104 
105        try
106        {
107            // Execute as specified user
108            authenticationContext.setUser(user);
109            JiraAuthenticationContextImpl.clearRequestCache();
110 
111            // We have the workflow transition id.
112            // Now we need to process and validate all the fields that can be set during the transition
113            WorkflowTransitionUtil workflowTransitionUtil = (WorkflowTransitionUtil) JiraUtils.loadComponent(WorkflowTransitionUtilAddOnImpl.class);
114            workflowTransitionUtil.setIssue(issue);
115            workflowTransitionUtil.setAction(actionId);
116            workflowTransitionUtil.setParams(params);
117 
118            // Validate input
119            JellyUtils.processErrorCollection(workflowTransitionUtil.validate());
120 
121            // Progress the issue through workflow
122            JellyUtils.processErrorCollection(workflowTransitionUtil.progress());
123        }
124        finally
125        {
126            // Restore the user
127            authenticationContext.setUser(previousUser);
128            JiraAuthenticationContextImpl.clearRequestCache();
129        }
130 
131    }
132 
133    private ActionDescriptor getActionId(Issue issue) throws JellyTagException
134    {
135        try
136        {
137            JiraWorkflow workflow = workflowManager.getWorkflow(issue.getGenericValue());
138 
139            try
140            {
141                int actionId = Integer.parseInt(getWorkflowActionProperty());
142                ActionDescriptor action = workflow.getDescriptor().getAction(actionId);
143                if (action == null)
144                    throw new JellyTagException("Invalid action id '" + getWorkflowActionProperty() + "'.");
145 
146                return action;
147            }
148            catch (NumberFormatException e)
149            {
150                return getActionIdByName(workflow);
151            }
152        }
153        catch (WorkflowException e)
154        {
155            throw new JellyTagException("Error occurred while retrieving workflow for issue with key '" + getKey() + "'", e);
156        }
157    }
158 
159    private MutableIssue getIssue() throws JellyTagException
160    {
161        try
162        {
163            GenericValue issue = issueManager.getIssue(getKey());
164            if (issue == null)
165            {
166                throw new JellyTagException("Cannot retrieve issue with key '" + getKey() + "'");
167            }
168 
169            return getIssueObject(issue);
170        }
171        catch (GenericEntityException e)
172        {
173            throw new JellyTagException("Error occurred while retrieving issue with key '" + getKey() + "'", e);
174        }
175    }
176 
177    public MutableIssue getIssueObject(GenericValue issueGV)
178    {
179        return issueFactory.getIssue(issueGV);
180    }
181 
182    private Map getTransitionParameters(Issue issue) throws JellyTagException
183    {
184        Map params = new HashMap();
185 
186        if (paramSpecified(KEY_COMMENT))
187        {
188            params.put(WorkflowTransitionUtil.FIELD_COMMENT, getComment());
189 
190            if (paramSpecified(KEY_COMMENT_LEVEL))
191            {
192                params.put(WorkflowTransitionUtil.FIELD_COMMENT_LEVEL, getCommentLevel());
193            }
194        }
195 
196        for (Iterator iterator = getProperties().keySet().iterator(); iterator.hasNext();)
197        {
198            String attributeName = (String) iterator.next();
199            // Check if attribute is one that is allowed by this tag
200            if (!attributes.contains(attributeName))
201            {
202                // Check if the attribute is an orderable field
203                if (fieldManager.isOrderableField(attributeName))
204                {
205                    // We have an orderable field. Use it to conver the passed text to parameters
206                    OrderableField orderableField = fieldManager.getOrderableField(attributeName);
207 
208                    if (fieldManager.isCustomField(orderableField))
209                    {
210                        Map actionParams = new HashMap();
211                        for (Iterator iterator1 = getProperties().keySet().iterator(); iterator1.hasNext();)
212                        {
213                            String key = (String) iterator1.next();
214                            if (key.startsWith(orderableField.getId()))
215                            {
216                                List values = (List) getProperties().get(key);
217                                actionParams.put(key, values.toArray(new String[values.size()]));
218                            }
219                        }
220 
221                        orderableField.populateFromParams(params, actionParams);
222                    }
223                    else
224                    {
225                        try
226                        {
227                            orderableField.populateParamsFromString(params, (String) getProperties().get(attributeName), issue);
228                        }
229                        catch (FieldValidationException e)
230                        {
231                            throw new JellyTagException(e.getMessage(), e);
232                        }
233                    }
234                }
235                else
236                {
237                    // Should not really ever get here as this check is done earlier by the validateAttributes() method
238                    throw new JellyTagException("Invalid attribute '" + attributeName + "'.");
239                }
240            }
241        }
242 
243        return params;
244    }
245 
246    private User getUser() throws JellyTagException
247    {
248        String username;
249        if (paramSpecified(KEY_USER))
250        {
251            username = getUsername();
252        }
253        else
254        {
255            // Get the user from context
256            username = (String) getContext().getVariable(JellyTagConstants.USERNAME);
257        }
258 
259        User user;
260        try
261        {
262            if (TextUtils.stringSet(username))
263            {
264                user = UserUtils.getUser(username);
265            }
266            else
267            {
268                user = null;
269            }
270        }
271        catch (EntityNotFoundException e)
272        {
273            throw new JellyTagException("The user '" + username + "' cannot be found.", e);
274        }
275 
276        return user;
277    }
278 
279    private void validateAttributes() throws JellyTagException
280    {
281        if (!paramSpecified(KEY_ISSUE_KEY) || !TextUtils.stringSet(getKey()))
282        {
283            throw new MissingAttributeException(KEY_ISSUE_KEY);
284        }
285 
286        if (!paramSpecified(KEY_WORKFLOW_ACTION) || !TextUtils.stringSet(getWorkflowActionProperty()))
287        {
288            throw new MissingAttributeException(KEY_WORKFLOW_ACTION);
289        }
290 
291        Issue issue = getIssue();
292 
293        ActionDescriptor actionDescriptor = getActionId(issue);
294        WorkflowActionsBean workflowActionsBean = new WorkflowActionsBean();
295        FieldScreen fieldScreen = workflowActionsBean.getFieldScreenForView(actionDescriptor);
296 
297        for (Iterator iterator = getProperties().keySet().iterator(); iterator.hasNext();)
298        {
299            String attributeName = (String) iterator.next();
300            // Check if attribute is one that is allowed by this tag
301            if (!attributes.contains(attributeName))
302            {
303                String fieldId = attributeName;
304                int index = attributeName.indexOf(":");
305                if (index > -1)
306                {
307                    fieldId = attributeName.substring(0, index);
308                }
309 
310                // Check if the attribute is an orderable field
311                if (!fieldManager.isOrderableField(fieldId))
312                {
313                    throw new JellyTagException("Invalid attribute '" + attributeName + "'.");
314                }
315                else if(fieldScreen == null)
316                {
317                    throw new JellyTagException("Field '" + attributeName + "' can not be set on action with no screen");
318                }
319                // Ensure the field actually appears on this screen
320                else if (!fieldScreen.containsField(fieldId))
321                {
322                    throw new JellyTagException("Field '" + attributeName + "' is not present on screen '" + fieldScreen.getName() + "'.");
323                }
324            }
325        }
326    }
327 
328    private ActionDescriptor getActionIdByName(JiraWorkflow workflow) throws JellyTagException
329    {
330        String workflowActionProperty = getWorkflowActionProperty();
331        // Try retrieving the action using its name
332        for (Iterator iterator = workflow.getAllActions().iterator(); iterator.hasNext();)
333        {
334            ActionDescriptor actionDescriptor = (ActionDescriptor) iterator.next();
335            if (workflowActionProperty.equalsIgnoreCase(actionDescriptor.getName()))
336            {
337                return actionDescriptor;
338            }
339        }
340 
341        throw new JellyTagException("Invalid action name '" + workflowActionProperty + "'.");
342    }
343 
344    private String getUsername()
345    {
346        return (String) getProperties().get(KEY_USER);
347    }
348 
349    public String getKey()
350    {
351        return (String) getProperties().get(KEY_ISSUE_KEY);
352    }
353 
354    public String getWorkflowActionProperty()
355    {
356        return (String) getProperties().get(KEY_WORKFLOW_ACTION);
357    }
358 
359    private String getComment()
360    {
361        return (String) getProperties().get(KEY_COMMENT);
362    }
363 
364    private String getCommentLevel()
365    {
366        return (String) getProperties().get(KEY_COMMENT_LEVEL);
367    }
368 
369    private boolean paramSpecified(String paramName)
370    {
371        return getProperties().containsKey(paramName);
372    }
373 
374    public void addCustomFieldValue(CustomField customField, String customFieldValue, String key)
375    {
376        final String customFieldId = customField.getId();
377        if (key != null)
378        {
379            getProperties().put(customFieldId + ":" + key, EasyList.build(customFieldValue));
380        }
381        else
382        {
383            if (paramSpecified(customFieldId))
384            {
385                // It's a multivalue, add to the list
386                Collection values = (Collection) getProperties().get(customFieldId);
387                values.add(customFieldValue);
388            }
389            else
390            {
391                getProperties().put(customFieldId, EasyList.build(customFieldValue));
392            }
393        }
394    }
395 }