View Javadoc

1   /**
2    * Copyright (C) 2008 Atlassian
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.atlassian.connector.commons.jira.soap;
18  
19  import com.atlassian.connector.commons.api.ConnectionCfg;
20  import com.atlassian.connector.commons.jira.JIRAAction;
21  import com.atlassian.connector.commons.jira.JIRAActionBean;
22  import com.atlassian.connector.commons.jira.JIRAActionField;
23  import com.atlassian.connector.commons.jira.JIRAActionFieldBean;
24  import com.atlassian.connector.commons.jira.JIRAIssue;
25  import com.atlassian.connector.commons.jira.JIRAIssueBean;
26  import com.atlassian.connector.commons.jira.JiraUserNotFoundException;
27  import com.atlassian.connector.commons.jira.beans.JIRAAttachment;
28  import com.atlassian.connector.commons.jira.beans.JIRAComment;
29  import com.atlassian.connector.commons.jira.beans.JIRACommentBean;
30  import com.atlassian.connector.commons.jira.beans.JIRAComponentBean;
31  import com.atlassian.connector.commons.jira.beans.JIRAConstant;
32  import com.atlassian.connector.commons.jira.beans.JIRAIssueTypeBean;
33  import com.atlassian.connector.commons.jira.beans.JIRAPriorityBean;
34  import com.atlassian.connector.commons.jira.beans.JIRAProject;
35  import com.atlassian.connector.commons.jira.beans.JIRAProjectBean;
36  import com.atlassian.connector.commons.jira.beans.JIRAQueryFragment;
37  import com.atlassian.connector.commons.jira.beans.JIRAResolutionBean;
38  import com.atlassian.connector.commons.jira.beans.JIRASavedFilterBean;
39  import com.atlassian.connector.commons.jira.beans.JIRASecurityLevelBean;
40  import com.atlassian.connector.commons.jira.beans.JIRAStatusBean;
41  import com.atlassian.connector.commons.jira.beans.JIRAUserBean;
42  import com.atlassian.connector.commons.jira.beans.JIRAVersionBean;
43  import com.atlassian.connector.commons.jira.cache.CacheConstants;
44  import com.atlassian.connector.commons.jira.soap.axis.JiraSoapService;
45  import com.atlassian.connector.commons.jira.soap.axis.JiraSoapServiceServiceLocator;
46  import com.atlassian.connector.commons.jira.soap.axis.RemoteAttachment;
47  import com.atlassian.connector.commons.jira.soap.axis.RemoteAuthenticationException;
48  import com.atlassian.connector.commons.jira.soap.axis.RemoteComment;
49  import com.atlassian.connector.commons.jira.soap.axis.RemoteComponent;
50  import com.atlassian.connector.commons.jira.soap.axis.RemoteField;
51  import com.atlassian.connector.commons.jira.soap.axis.RemoteFieldValue;
52  import com.atlassian.connector.commons.jira.soap.axis.RemoteFilter;
53  import com.atlassian.connector.commons.jira.soap.axis.RemoteIssue;
54  import com.atlassian.connector.commons.jira.soap.axis.RemoteIssueType;
55  import com.atlassian.connector.commons.jira.soap.axis.RemoteNamedObject;
56  import com.atlassian.connector.commons.jira.soap.axis.RemotePriority;
57  import com.atlassian.connector.commons.jira.soap.axis.RemoteProject;
58  import com.atlassian.connector.commons.jira.soap.axis.RemoteResolution;
59  import com.atlassian.connector.commons.jira.soap.axis.RemoteSecurityLevel;
60  import com.atlassian.connector.commons.jira.soap.axis.RemoteStatus;
61  import com.atlassian.connector.commons.jira.soap.axis.RemoteUser;
62  import com.atlassian.connector.commons.jira.soap.axis.RemoteVersion;
63  import com.atlassian.connector.commons.jira.soap.axis.RemoteWorklog;
64  import com.atlassian.theplugin.commons.configuration.ConfigurationFactory;
65  import com.atlassian.theplugin.commons.remoteapi.RemoteApiException;
66  import com.atlassian.theplugin.commons.remoteapi.RemoteApiLoginException;
67  import com.atlassian.theplugin.commons.remoteapi.rest.AbstractHttpSession;
68  import com.atlassian.theplugin.commons.util.HttpConfigurableAdapter;
69  import com.atlassian.theplugin.commons.util.Logger;
70  import org.apache.axis.AxisProperties;
71  import org.apache.commons.codec.binary.Base64;
72  import org.xml.sax.SAXException;
73  
74  import javax.xml.rpc.ServiceException;
75  import java.net.MalformedURLException;
76  import java.net.URL;
77  import java.rmi.RemoteException;
78  import java.util.ArrayList;
79  import java.util.Calendar;
80  import java.util.Collection;
81  import java.util.List;
82  import java.util.concurrent.CopyOnWriteArrayList;
83  
84  public class JIRASessionImpl implements JIRASession {
85  
86      private String token;
87      private final JiraSoapService service;
88      private final ConnectionCfg httpConnectionCfg;
89      private boolean loggedIn;
90      private final Logger logger;
91  
92      //
93      // AxisProperties are shit - if you try to set nonexistent property to null, NPE is thrown. Moreover, sometimes
94      // setting apparently *existing* property to null also throws NPE (see bug PL-412)! Crap, crap, crap...
95      //
96      private void setAxisProperty(String name, String value) {
97          if (value == null) {
98              if (AxisProperties.getProperty(name) != null) {
99                  try {
100                     AxisProperties.setProperty(name, "");
101                 } catch (NullPointerException e) {
102                     logger.info("Setting AXIS property " + name + " to empty", e);
103                 }
104             }
105         } else {
106             AxisProperties.setProperty(name, value);
107         }
108     }
109 
110     private void setSystemProperty(String name, String value) {
111 
112         if (value == null) {
113             //if (System.getProperty(name) != null) {
114             try {
115                 System.setProperty(name, "");
116             } catch (NullPointerException e) {
117                 logger.info("Setting system property " + name + " to empty", e);
118             }
119             //}
120         } else {
121             System.setProperty(name, value);
122         }
123     }
124 
125     private void setProxy() {
126         boolean useIdeaProxySettings =
127                 ConfigurationFactory.getConfiguration().getGeneralConfigurationData().getUseIdeaProxySettings();
128         HttpConfigurableAdapter proxyInfo = ConfigurationFactory.getConfiguration().transientGetHttpConfigurable();
129         String host = null;
130         String port = null;
131         String user = null;
132         String password = null;
133         if (useIdeaProxySettings && proxyInfo.isUseHttpProxy()) {
134             host = proxyInfo.getProxyHost();
135             port = String.valueOf(proxyInfo.getProxyPort());
136             if (proxyInfo.isProxyAuthentication()) {
137                 user = proxyInfo.getProxyLogin();
138                 password = proxyInfo.getPlainProxyPassword();
139             }
140         }
141 
142         //
143         // well - re-setting proxy does not really work - Axis bug
144         // see: http://issues.apache.org/jira/browse/AXIS-2295
145         // So in order to apply new proxy settings, IDEA has to be restarted
146         // all software sucks
147         //
148         setAxisProperty("http.proxyHost", host);
149         setAxisProperty("http.proxyPort", port);
150 
151         setSystemProperty("http.proxyHost", host);
152         setSystemProperty("http.proxyPort", port);
153 
154         setAxisProperty("http.proxyUser", user);
155         setSystemProperty("http.proxyUser", user);
156 
157         setAxisProperty("http.proxyPassword", password);
158         setSystemProperty("http.proxyPassword", password);
159 
160     }
161 
162     public JIRASessionImpl(Logger logger, ConnectionCfg connectionCfg, AxisSessionCallback callback)
163             throws ServiceException, MalformedURLException {
164         this.logger = logger;
165         URL portAddress = new URL(connectionCfg.getUrl() + "/rpc/soap/jirasoapservice-v2");
166         JiraSoapServiceServiceLocator loc = new JiraSoapServiceServiceLocator();
167         AbstractHttpSession.setUrl(portAddress); // dirty hack
168         service = loc.getJirasoapserviceV2(portAddress);
169         // to use Basic HTTP Authentication:
170         if (callback != null) {
171             callback.configureRemoteService(service, connectionCfg);
172         }
173  
174         setProxy();
175 
176         this.httpConnectionCfg = connectionCfg;
177     }
178 
179     public void login(String userName, String password) throws RemoteApiException {
180         try {
181             token = service.login(userName, password);
182         } catch (RemoteAuthenticationException e) {
183             throw new RemoteApiLoginException("Authentication failed");
184         } catch (RemoteException e) {
185             throw new RemoteApiException(e.toString());
186         }
187         loggedIn = true;
188     }
189 
190     public void logout() {
191         try {
192             if (service.logout(token)) {
193                 token = null;
194                 loggedIn = false;
195             }
196         } catch (java.rmi.RemoteException e) {
197             // todo: log the exception
198         }
199     }
200 
201     public void logWork(JIRAIssue issue, String timeSpent, Calendar startDate, String comment,
202                         boolean updateEstimate, String newEstimate)
203             throws RemoteApiException {
204         RemoteWorklog workLog = new RemoteWorklog();
205         workLog.setStartDate(startDate);
206         workLog.setTimeSpent(timeSpent);
207         if (comment != null) {
208             workLog.setComment(comment);
209         }
210         try {
211             if (updateEstimate) {
212                 if (newEstimate != null) {
213                     service.addWorklogWithNewRemainingEstimate(token, issue.getKey(), workLog, newEstimate);
214                 } else {
215                     service.addWorklogAndAutoAdjustRemainingEstimate(token, issue.getKey(), workLog);
216                 }
217             } else {
218                 service.addWorklogAndRetainRemainingEstimate(token, issue.getKey(), workLog);
219             }
220         } catch (RemoteException e) {
221             throw new RemoteApiException(e.toString(), e);
222         }
223     }
224 
225     public JIRAIssue createIssue(JIRAIssue issue) throws RemoteApiException {
226         RemoteIssue remoteIssue = new RemoteIssue();
227 
228         remoteIssue.setProject(issue.getProjectKey());
229         remoteIssue.setType(String.valueOf(issue.getTypeConstant().getId()));
230         remoteIssue.setSummary(issue.getSummary());
231         if (issue.getPriorityConstant().getId() != CacheConstants.ANY_ID) {
232             remoteIssue.setPriority(String.valueOf(issue.getPriorityConstant().getId()));
233         }
234 
235         if (issue.getDescription() != null) {
236             remoteIssue.setDescription(issue.getDescription());
237         }
238         if (issue.getAssignee() != null) {
239             remoteIssue.setAssignee(issue.getAssignee());
240         }
241 
242         final List<JIRAConstant> components = issue.getComponents();
243         if (components != null && components.size() > 0) {
244             RemoteComponent[] remoteComponents = new RemoteComponent[components.size()];
245             int i = 0;
246             for (JIRAConstant component : components) {
247                 remoteComponents[i] = new RemoteComponent(String.valueOf(component.getId()), component.getName());
248                 i++;
249             }
250             remoteIssue.setComponents(remoteComponents);
251         }
252 
253         final List<JIRAConstant> versions = issue.getAffectsVersions();
254         if (versions != null && versions.size() > 0) {
255             RemoteVersion[] remoteVersions = new RemoteVersion[versions.size()];
256             int i = 0;
257             for (JIRAConstant version : versions) {
258                 remoteVersions[i] = new RemoteVersion();
259                 remoteVersions[i].setId(String.valueOf(version.getId()));
260                 ++i;
261             }
262             remoteIssue.setAffectsVersions(remoteVersions);
263         }
264 
265         final List<JIRAConstant> fixVersions = issue.getFixVersions();
266         if (fixVersions != null && fixVersions.size() > 0) {
267             RemoteVersion[] remoteFixVersions = new RemoteVersion[fixVersions.size()];
268             int i = 0;
269             for (JIRAConstant version : fixVersions) {
270                 remoteFixVersions[i] = new RemoteVersion();
271                 remoteFixVersions[i].setId(String.valueOf(version.getId()));
272                 ++i;
273             }
274             remoteIssue.setFixVersions(remoteFixVersions);
275         }
276 
277         try {
278             remoteIssue = service.createIssue(token, remoteIssue);
279         } catch (RemoteException e) {
280             throw new RemoteApiException(e.toString(), e);
281         }
282 
283         // todo: fill in all other fields. For now only the issue key and URL is being displayed
284         JIRAIssueBean retVal = new JIRAIssueBean(httpConnectionCfg.getUrl(), remoteIssue);
285 
286         retVal.setKey(remoteIssue.getKey());
287         return retVal;
288     }
289 
290     public JIRAIssue getIssueDetails(JIRAIssue issue) throws RemoteApiException {
291         RemoteSecurityLevel securityLevel = null;
292 
293         try {
294             securityLevel = service.getSecurityLevel(token, issue.getKey());
295         } catch (RemoteException e) {
296             if (logger != null) {
297                 logger.warn(
298                         "Soap method 'getSecurityLevel' thrown exception. "
299                                 + "Probably there is no 'SecurityLevel' on JIRA (non enterprise version of JIRA).", e);
300             }
301         } catch (ClassCastException e) {
302             if (logger != null) {
303                 logger.warn(
304                         "Soap method 'getSecurityLevel' thrown ClassCastException. Probably some JIRA error.", e);
305             }
306         } catch (Exception e) {
307             // PL-1492 and PL-1609
308             if (e instanceof SAXException && logger != null) {
309                 logger.warn(
310                         "Soap method 'getSecurityLevel' thrown SAXException. Probably some JIRA error.", e);
311             }
312             throw new RemoteApiException(e);
313         }
314 
315         try {
316             RemoteIssue rIssue = service.getIssue(token, issue.getKey());
317 
318 
319             if (rIssue == null) {
320                 throw new RemoteApiException("Unable to retrieve issue details");
321             }
322             JIRAIssueBean issueBean = new JIRAIssueBean(issue);
323 
324             if (securityLevel != null) {
325                 issueBean.setSecurityLevel(
326                         new JIRASecurityLevelBean(Long.valueOf(securityLevel.getId()), securityLevel.getName()));
327             }
328 
329             RemoteVersion[] aVers = rIssue.getAffectsVersions();
330             List<JIRAConstant> av = new ArrayList<JIRAConstant>();
331             for (RemoteVersion v : aVers) {
332                 av.add(new JIRAVersionBean(Long.valueOf(v.getId()), v.getName(), v.isReleased()));
333             }
334             issueBean.setAffectsVersions(av);
335 
336             RemoteVersion[] fVers = rIssue.getFixVersions();
337             List<JIRAConstant> fv = new ArrayList<JIRAConstant>();
338             for (RemoteVersion v : fVers) {
339                 fv.add(new JIRAVersionBean(Long.valueOf(v.getId()), v.getName(), v.isReleased()));
340             }
341             issueBean.setFixVersions(fv);
342 
343             RemoteComponent[] comps = rIssue.getComponents();
344             List<JIRAConstant> c = new ArrayList<JIRAConstant>();
345             for (RemoteComponent rc : comps) {
346                 c.add(new JIRAComponentBean(Long.valueOf(rc.getId()), rc.getName()));
347             }
348             issueBean.setComponents(c);
349 
350             issueBean.setProjectKey(rIssue.getProject());
351             issueBean.setSummary(rIssue.getSummary());
352 
353             issueBean.setRawSoapIssue(rIssue);
354 
355             return issueBean;
356 
357         } catch (RemoteException e) {
358             throw new RemoteApiException(e.toString(), e);
359         } catch (ClassCastException e) {
360             throw new RemoteApiException(e.toString(), e);
361         } catch (Exception e) {
362             // PL-1644 - there is this bloke who seems to have some customized RPC plugin,
363             // which returns non-standard fields in SOAP response. Axis croaks on it,
364             // we have to intercept
365             if (e instanceof SAXException && logger != null) {
366                 logger.warn("Soap method 'getIssue' thrown SAXException. "
367                         + "Probably some JIRA error or weird JIRA SOAP plugin.", e);
368             }
369             throw new RemoteApiException(e);
370         }
371 
372     }
373 
374     public void addComment(String issueKey, String comment) throws RemoteApiException {
375         try {
376             RemoteComment rComment = new RemoteComment();
377             rComment.setBody(comment);
378             service.addComment(token, issueKey, rComment);
379         } catch (RemoteException e) {
380             throw new RemoteApiException(e.toString(), e);
381         }
382     }
383 
384     public void addAttachment(String issueKey, String name, byte[] content) throws RemoteApiException {
385 		String[] encodedContents = new String[]{new String(new Base64().encode(content))};
386 		String[] names = new String[]{name};
387 		try {
388 			service.addBase64EncodedAttachmentsToIssue(token, issueKey, names, encodedContents);
389 		} catch (RemoteException e) {
390 			if (e.toString().startsWith("java.lang.OutOfMemoryError")) {
391 				throw new RemoteApiException("Attachment size is too large, try uploading directly from web browser", e);
392 			}
393 			throw new RemoteApiException(e.toString(), e);
394         }
395     }
396 
397     public List<JIRAProject> getProjects() throws RemoteApiException {
398         try {
399             RemoteProject[] projects = service.getProjectsNoSchemes(token);
400             List<JIRAProject> projectList = new ArrayList<JIRAProject>(projects.length);
401             if (projects != null) {
402                 for (RemoteProject p : projects) {
403                     JIRAProjectBean project = new JIRAProjectBean();
404 
405                     project.setName(p.getName());
406                     project.setKey(p.getKey());
407                     project.setDescription(p.getDescription());
408                     project.setUrl(p.getUrl());
409                     project.setLead(p.getLead());
410                     project.setId(Long.valueOf(p.getId()));
411 
412                     projectList.add(project);
413                 }
414             }
415 
416             return projectList;
417         } catch (RemoteException e) {
418             throw new RemoteApiException(e.toString(), e);
419         }
420     }
421 
422     private List<JIRAConstant> issueTableToList(RemoteIssueType[] types) throws MalformedURLException {
423         List<JIRAConstant> typesList = new ArrayList<JIRAConstant>();
424         for (RemoteIssueType type : types) {
425             typesList.add(new JIRAIssueTypeBean(Long.valueOf(type.getId()), type.getName(), new URL(type.getIcon())));
426         }
427         return typesList;
428     }
429 
430     public List<JIRAConstant> getIssueTypes() throws RemoteApiException {
431         try {
432             return issueTableToList(service.getIssueTypes(token));
433         } catch (RemoteException e) {
434             throw new RemoteApiException(e.toString(), e);
435         } catch (MalformedURLException e) {
436             throw new RemoteApiException(e.toString(), e);
437         }
438 
439     }
440 
441     public List<JIRAConstant> getIssueTypesForProject(String project) throws RemoteApiException {
442         try {
443             return issueTableToList(service.getIssueTypesForProject(token, project));
444         } catch (RemoteException e) {
445             throw new RemoteApiException(e.toString(), e);
446         } catch (MalformedURLException e) {
447             throw new RemoteApiException(e.toString(), e);
448         }
449     }
450 
451     public List<JIRAConstant> getSubtaskIssueTypes() throws RemoteApiException {
452         try {
453             return issueTableToList(service.getSubTaskIssueTypes(token));
454         } catch (RemoteException e) {
455             throw new RemoteApiException(e.toString(), e);
456         } catch (MalformedURLException e) {
457             throw new RemoteApiException(e.toString(), e);
458         }
459 
460     }
461 
462     public List<JIRAConstant> getSubtaskIssueTypesForProject(String project) throws RemoteApiException {
463         try {
464             return issueTableToList(service.getSubTaskIssueTypesForProject(token, project));
465         } catch (RemoteException e) {
466             throw new RemoteApiException(e.toString(), e);
467         } catch (MalformedURLException e) {
468             throw new RemoteApiException(e.toString(), e);
469         }
470     }
471 
472     public List<JIRAConstant> getStatuses() throws RemoteApiException {
473         try {
474             RemoteStatus[] statuses = service.getStatuses(token);
475 
476             List<JIRAConstant> statusesList = new ArrayList<JIRAConstant>(statuses.length);
477             for (RemoteStatus status : statuses) {
478                 statusesList.add(new JIRAStatusBean(
479                         Long.valueOf(status.getId()), status.getName(), new URL(status.getIcon())));
480             }
481             return statusesList;
482         } catch (RemoteException e) {
483             throw new RemoteApiException(e.toString(), e);
484         } catch (MalformedURLException e) {
485             throw new RemoteApiException(e.toString(), e);
486         }
487     }
488 
489     public List<JIRAComponentBean> getComponents(String projectKey) throws RemoteApiException {
490         try {
491             RemoteComponent[] components = service.getComponents(token, projectKey);
492 
493             List<JIRAComponentBean> componentsList = new ArrayList<JIRAComponentBean>(components.length);
494             for (RemoteComponent c : components) {
495                 componentsList.add(new JIRAComponentBean(Long.valueOf(c.getId()), c.getName()));
496             }
497             return componentsList;
498         } catch (RemoteException e) {
499             throw new RemoteApiException(e.toString(), e);
500         }
501     }
502 
503     public List<JIRAVersionBean> getVersions(String projectKey) throws RemoteApiException {
504         try {
505             RemoteVersion[] versions = service.getVersions(token, projectKey);
506 
507             List<JIRAVersionBean> versionsList = new ArrayList<JIRAVersionBean>(versions.length);
508             for (RemoteVersion v : versions) {
509                 versionsList.add(new JIRAVersionBean(Long.valueOf(v.getId()), v.getName(), v.isReleased()));
510             }
511             return versionsList;
512         } catch (RemoteException e) {
513             throw new RemoteApiException(
514                     e.toString() != null ? e.toString() : "Cannot fetch project '" + projectKey + "' versions", e);
515         }
516     }
517 
518     public List<JIRAPriorityBean> getPriorities() throws RemoteApiException {
519         try {
520             RemotePriority[] priorities = service.getPriorities(token);
521 
522             List<JIRAPriorityBean> prioritiesList = new ArrayList<JIRAPriorityBean>(priorities.length);
523             int i = 0;
524             for (RemotePriority p : priorities) {
525                 // PL-1164 - The "i" parameter defines the order in which priorities
526                 // are shown in the issue tree. I am assuming that JIRA returns the
527                 // list of priorities in the order that the user defined, and not
528                 // in some random order. This does seem to be the case with my test httpConnectionCfg
529                 prioritiesList.add(new JIRAPriorityBean(Long.valueOf(p.getId()), i, p.getName(), new URL(p.getIcon())));
530                 ++i;
531             }
532             return prioritiesList;
533         } catch (RemoteException e) {
534             throw new RemoteApiException(e.toString(), e);
535         } catch (MalformedURLException e) {
536             throw new RemoteApiException(e.toString(), e);
537         }
538     }
539 
540     public List<JIRAResolutionBean> getResolutions() throws RemoteApiException {
541         try {
542             RemoteResolution[] resolutions = service.getResolutions(token);
543 
544             List<JIRAResolutionBean> resolutionsList = new ArrayList<JIRAResolutionBean>(resolutions.length);
545             for (RemoteResolution p : resolutions) {
546                 resolutionsList.add(new JIRAResolutionBean(Long.valueOf(p.getId()), p.getName()));
547             }
548             return resolutionsList;
549         } catch (RemoteException e) {
550             throw new RemoteApiException(e.toString(), e);
551         }
552     }
553 
554     public List<JIRAQueryFragment> getSavedFilters() throws RemoteApiException {
555         try {
556             RemoteFilter[] filters = service.getSavedFilters(token);
557 
558             List<JIRAQueryFragment> filtersList = new ArrayList<JIRAQueryFragment>(filters.length);
559             for (RemoteFilter f : filters) {
560                 filtersList.add(new JIRASavedFilterBean(f.getName(), Long.valueOf(f.getId())));
561             }
562             return filtersList;
563         } catch (RemoteException e) {
564             throw new RemoteApiException(e.toString(), e);
565         }
566 
567     }
568 
569 	public void setField(JIRAIssue issue, String fieldId, String value) throws RemoteApiException {
570 		setField(issue, fieldId, new String[]{value});
571 	}
572 
573 	public void setField(JIRAIssue issue, String fieldId, String[] values) throws RemoteApiException {
574 		RemoteFieldValue v = new RemoteFieldValue();
575 		RemoteFieldValue[] vTable = {v};
576 		v.setId(fieldId);
577 		v.setValues(values);
578 		try {
579 			service.updateIssue(token, issue.getKey(), vTable);
580 		} catch (RemoteException e) {
581 			throw new RemoteApiException(e.toString(), e);
582 		}
583 	}
584 
585 	public void setFields(JIRAIssue issue, List<JIRAActionField> fields) throws RemoteApiException {
586 		RemoteFieldValue[] vTable = new RemoteFieldValue[fields.size()];
587 		int i = 0;
588 		for (JIRAActionField field : fields) {
589 			vTable[i] = new RemoteFieldValue();
590 			vTable[i].setId(field.getFieldId());
591 			vTable[i].setValues(field.getValues().toArray(new String[field.getValues().size()]));
592 			i++;
593 		}
594 		try {
595 			service.updateIssue(token, issue.getKey(), vTable);
596 		} catch (RemoteException e) {
597 			throw new RemoteApiException(e.toString(), e);
598 		}
599 	}
600 
601     public List<JIRAAction> getAvailableActions(JIRAIssue issue) throws RemoteApiException {
602         try {
603             RemoteNamedObject[] actions = service.getAvailableActions(token, issue.getKey());
604             List<JIRAAction> actionList = new ArrayList<JIRAAction>(actions != null ? actions.length : 0);
605             if (actions != null) {
606                 for (RemoteNamedObject action : actions) {
607                     actionList.add(new JIRAActionBean(Long.valueOf(action.getId()), action.getName()));
608                 }
609             }
610             return actionList;
611         } catch (RemoteException e) {
612             throw new RemoteApiException(e.toString(), e);
613         } catch (ClassCastException e) {
614             throw new RemoteApiException(e.toString(), e);
615         } catch (Exception e) {
616             // PL-1609
617             if (e instanceof SAXException && logger != null) {
618                 logger.warn(
619                         "Soap method 'getSecurityLevel' thrown SAXException. Probably some JIRA error.", e);
620             }
621             throw new RemoteApiException(e);
622         }
623     }
624 
625     public List<JIRAActionField> getFieldsForAction(JIRAIssue issue, JIRAAction action) throws RemoteApiException {
626         try {
627             RemoteField[] fields = service.getFieldsForAction(
628                     token, issue.getKey(), Long.valueOf(action.getId()).toString());
629             List<JIRAActionField> fieldList = new ArrayList<JIRAActionField>(fields.length);
630             for (RemoteField f : fields) {
631                 fieldList.add(new JIRAActionFieldBean(f.getId(), f.getName()));
632             }
633             return fieldList;
634         } catch (RemoteException e) {
635             throw new RemoteApiException(e.toString(), e);
636         }
637     }
638 
639     public void progressWorkflowAction(JIRAIssue issue, JIRAAction action, List<JIRAActionField> fields)
640             throws RemoteApiException {
641         try {
642             if (fields == null) {
643                 RemoteFieldValue[] dummyValues = new RemoteFieldValue[0];
644                 service.progressWorkflowAction(token, issue.getKey(), String.valueOf(action.getId()), dummyValues);
645             } else {
646 
647                 CopyOnWriteArrayList<JIRAActionField> safeFields = new CopyOnWriteArrayList<JIRAActionField>(fields);
648 
649                 for (JIRAActionField field : safeFields) {
650                     if (field.getValues() == null) {
651                         safeFields.remove(field);
652                     }
653                 }
654 
655                 int i = 0;
656                 RemoteFieldValue[] values = new RemoteFieldValue[safeFields.size()];
657 
658                 for (JIRAActionField field : safeFields) {
659                     List<String> fieldValues = field.getValues();
660                     String[] fieldValueTable = fieldValues.toArray(new String[fieldValues.size()]);
661                     values[i] = new RemoteFieldValue(field.getFieldId(), fieldValueTable);
662                     ++i;
663                 }
664                 service.progressWorkflowAction(token, issue.getKey(), String.valueOf(action.getId()), values);
665             }
666         } catch (RemoteException e) {
667             throw new RemoteApiException(e.toString(), e);
668         }
669     }
670 
671     public List<JIRAComment> getComments(JIRAIssue issue) throws RemoteApiException {
672         try {
673             RemoteComment[] comments = service.getComments(token, issue.getKey());
674             if (comments == null) {
675                 throw new RemoteApiException("Unable to retrieve comments");
676             }
677 
678             List<JIRAComment> commentsList = new ArrayList<JIRAComment>(comments.length);
679             for (RemoteComment c : comments) {
680                 commentsList.add(new JIRACommentBean(c.getId(), c.getAuthor(), c.getBody(), c.getCreated()));
681             }
682             return commentsList;
683         } catch (RemoteException e) {
684             throw new RemoteApiException(e.toString(), e);
685         } catch (IllegalArgumentException e) {
686             // PL-1756
687             throw new RemoteApiException(e.toString(), e);
688         }
689     }
690 
691     public JIRAUserBean getUser(String loginName) throws RemoteApiException, JiraUserNotFoundException {
692         try {
693             RemoteUser ru = service.getUser(token, loginName);
694             if (ru == null) {
695                 throw new JiraUserNotFoundException("User Name for " + loginName + " not found");
696             }
697             return new JIRAUserBean(-1, ru.getFullname(), ru.getName()) {
698                 @Override
699                 public String getQueryStringFragment() {
700                     return null;
701                 }
702 
703                 public JIRAQueryFragment getClone() {
704                     return null;
705                 }
706             };
707         } catch (RemoteException e) {
708             throw new RemoteApiException(e.toString(), e);
709         } catch (ClassCastException e) {
710             throw new RemoteApiException(e.toString(), e);
711         }
712     }
713 
714     public boolean isLoggedIn() {
715         return loggedIn;
716     }
717 
718     public Collection<JIRAAttachment> getIssueAttachements(JIRAIssue issue) throws RemoteApiException {
719         try {
720             RemoteAttachment[] attachements = service.getAttachmentsFromIssue(token, issue.getKey());
721 
722             List<JIRAAttachment> attachmentList = new ArrayList<JIRAAttachment>(attachements.length);
723             for (RemoteAttachment a : attachements) {
724                 attachmentList.add(new JIRAAttachment(a.getId(),
725                         a.getAuthor(), a.getFilename(), a.getFilesize(),
726                         a.getMimetype(), a.getCreated()));
727             }
728             return attachmentList;
729         } catch (RemoteException e) {
730             throw new RemoteApiException(e.toString(), e);
731         } catch (ClassCastException e) {
732             throw new RemoteApiException("Soap axis remote request failed to properly cast response while "
733                     + "acquiring issue attachments", e);
734         }
735     }
736 }