1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.atlassian.jira.rest.client.internal.jersey;
18
19 import com.atlassian.jira.rest.client.IssueRestClient;
20 import com.atlassian.jira.rest.client.MetadataRestClient;
21 import com.atlassian.jira.rest.client.ProgressMonitor;
22 import com.atlassian.jira.rest.client.RestClientException;
23 import com.atlassian.jira.rest.client.SessionRestClient;
24 import com.atlassian.jira.rest.client.domain.Issue;
25 import com.atlassian.jira.rest.client.domain.ServerInfo;
26 import com.atlassian.jira.rest.client.domain.Session;
27 import com.atlassian.jira.rest.client.domain.Transition;
28 import com.atlassian.jira.rest.client.domain.Votes;
29 import com.atlassian.jira.rest.client.domain.Watchers;
30 import com.atlassian.jira.rest.client.domain.input.AttachmentInput;
31 import com.atlassian.jira.rest.client.domain.input.FieldInput;
32 import com.atlassian.jira.rest.client.domain.input.LinkIssuesInput;
33 import com.atlassian.jira.rest.client.domain.input.TransitionInput;
34 import com.atlassian.jira.rest.client.internal.ServerVersionConstants;
35 import com.atlassian.jira.rest.client.internal.json.IssueJsonParser;
36 import com.atlassian.jira.rest.client.internal.json.JsonParser;
37 import com.atlassian.jira.rest.client.internal.json.TransitionJsonParser;
38 import com.atlassian.jira.rest.client.internal.json.TransitionJsonParserV5;
39 import com.atlassian.jira.rest.client.internal.json.VotesJsonParser;
40 import com.atlassian.jira.rest.client.internal.json.WatchersJsonParserBuilder;
41 import com.atlassian.jira.rest.client.internal.json.gen.CommentJsonGenerator;
42 import com.atlassian.jira.rest.client.internal.json.gen.LinkIssuesInputGenerator;
43 import com.google.common.collect.Lists;
44 import com.sun.jersey.api.client.WebResource;
45 import com.sun.jersey.client.apache.ApacheHttpClient;
46 import com.sun.jersey.core.header.FormDataContentDisposition;
47 import com.sun.jersey.multipart.BodyPart;
48 import com.sun.jersey.multipart.MultiPart;
49 import com.sun.jersey.multipart.MultiPartMediaTypes;
50 import com.sun.jersey.multipart.file.FileDataBodyPart;
51 import org.codehaus.jettison.json.JSONArray;
52 import org.codehaus.jettison.json.JSONException;
53 import org.codehaus.jettison.json.JSONObject;
54
55 import javax.annotation.Nullable;
56 import javax.ws.rs.core.MediaType;
57 import javax.ws.rs.core.UriBuilder;
58 import java.io.File;
59 import java.io.InputStream;
60 import java.net.URI;
61 import java.util.ArrayList;
62 import java.util.Collection;
63 import java.util.Iterator;
64 import java.util.concurrent.Callable;
65
66
67
68
69
70
71 public class JerseyIssueRestClient extends AbstractJerseyRestClient implements IssueRestClient {
72
73 private static final String FILE_ATTACHMENT_CONTROL_NAME = "file";
74 private final SessionRestClient sessionRestClient;
75 private final MetadataRestClient metadataRestClient;
76
77 private final IssueJsonParser issueParser = new IssueJsonParser();
78 private final JsonParser<Watchers> watchersParser = WatchersJsonParserBuilder.createWatchersParser();
79 private final TransitionJsonParser transitionJsonParser = new TransitionJsonParser();
80 private final JsonParser<Transition> transitionJsonParserV5 = new TransitionJsonParserV5();
81 private final VotesJsonParser votesJsonParser = new VotesJsonParser();
82 private ServerInfo serverInfo;
83
84 public JerseyIssueRestClient(URI baseUri, ApacheHttpClient client, SessionRestClient sessionRestClient, MetadataRestClient metadataRestClient) {
85 super(baseUri, client);
86 this.sessionRestClient = sessionRestClient;
87 this.metadataRestClient = metadataRestClient;
88 }
89
90 private synchronized ServerInfo getVersionInfo(ProgressMonitor progressMonitor) {
91 if (serverInfo == null) {
92 serverInfo = metadataRestClient.getServerInfo(progressMonitor);
93 }
94 return serverInfo;
95 }
96
97 @Override
98 public Watchers getWatchers(URI watchersUri, ProgressMonitor progressMonitor) {
99 return getAndParse(watchersUri, watchersParser, progressMonitor);
100 }
101
102
103 @Override
104 public Votes getVotes(URI votesUri, ProgressMonitor progressMonitor) {
105 return getAndParse(votesUri, votesJsonParser, progressMonitor);
106 }
107
108 @Override
109 public Issue getIssue(final String issueKey, ProgressMonitor progressMonitor) {
110 final UriBuilder uriBuilder = UriBuilder.fromUri(baseUri);
111 uriBuilder.path("issue").path(issueKey).queryParam("expand", IssueJsonParser.NAMES_SECTION +"," + IssueJsonParser.SCHEMA_SECTION + "," + IssueJsonParser.TRANSITIONS_SECTION);
112 return getAndParse(uriBuilder.build(), issueParser, progressMonitor);
113 }
114
115 @Override
116 public Iterable<Transition> getTransitions(final URI transitionsUri, ProgressMonitor progressMonitor) {
117 return invoke(new Callable<Iterable<Transition>>() {
118 @Override
119 public Iterable<Transition> call() throws Exception {
120 final WebResource transitionsResource = client.resource(transitionsUri);
121 final JSONObject jsonObject = transitionsResource.get(JSONObject.class);
122 if (jsonObject.has("transitions")) {
123 final JSONArray transitionsArray = jsonObject.getJSONArray("transitions");
124 final Collection<Transition> transitions = new ArrayList<Transition>(transitionsArray.length());
125 for(int i = 0, s = transitionsArray.length(); i < s; ++i) {
126 try {
127 final Transition transition = transitionJsonParserV5.parse(transitionsArray.getJSONObject(i));
128 transitions.add(transition);
129 } catch (JSONException e) {
130 throw new RestClientException(e);
131 }
132 }
133 return transitions;
134 } else {
135 final Collection<Transition> transitions = new ArrayList<Transition>(jsonObject.length());
136 @SuppressWarnings("unchecked")
137 final Iterator<String> iterator = jsonObject.keys();
138 while (iterator.hasNext()) {
139 final String key = iterator.next();
140 try {
141 final int id = Integer.parseInt(key);
142 final Transition transition = transitionJsonParser.parse(jsonObject.getJSONObject(key), id);
143 transitions.add(transition);
144 } catch (JSONException e) {
145 throw new RestClientException(e);
146 } catch (NumberFormatException e) {
147 throw new RestClientException("Transition id should be an integer, but found [" + key + "]", e);
148 }
149 }
150 return transitions;
151 }
152 }
153 });
154 }
155
156 @Override
157 public Iterable<Transition> getTransitions(final Issue issue, ProgressMonitor progressMonitor) {
158 if (issue.getTransitionsUri() != null) {
159 return getTransitions(issue.getTransitionsUri(), progressMonitor);
160 } else {
161 final UriBuilder transitionsUri = UriBuilder.fromUri(issue.getSelf());
162 return getTransitions(transitionsUri.path("transitions").queryParam("expand", "transitions.fields").build(), progressMonitor);
163 }
164 }
165
166 @Override
167 public void transition(final URI transitionsUri, final TransitionInput transitionInput, final ProgressMonitor progressMonitor) {
168 final int buildNumber = getVersionInfo(progressMonitor).getBuildNumber();
169 invoke(new Callable<Void>() {
170 @Override
171 public Void call() throws Exception {
172 JSONObject jsonObject = new JSONObject();
173 if (buildNumber >= ServerVersionConstants.BN_JIRA_5) {
174 jsonObject.put("transition", new JSONObject().put("id", transitionInput.getId()));
175 } else {
176 jsonObject.put("transition", transitionInput.getId());
177 }
178 if (transitionInput.getComment() != null) {
179 if (buildNumber >= ServerVersionConstants.BN_JIRA_5) {
180 jsonObject.put("update", new JSONObject().put("comment",
181 new JSONArray().put(new JSONObject().put("add",
182 new CommentJsonGenerator(getVersionInfo(progressMonitor), "group")
183 .generate(transitionInput.getComment())))));
184 } else {
185 jsonObject.put("comment", new CommentJsonGenerator(getVersionInfo(progressMonitor), "group").generate(transitionInput.getComment()));
186 }
187 }
188 JSONObject fieldsJs = new JSONObject();
189 final Iterable<FieldInput> fields = transitionInput.getFields();
190 if (fields.iterator().hasNext()) {
191 for (FieldInput fieldInput : fields) {
192 fieldsJs.put(fieldInput.getId(), fieldInput.getValue());
193 }
194 }
195 if (fieldsJs.keys().hasNext()) {
196 jsonObject.put("fields", fieldsJs);
197 }
198 final WebResource issueResource = client.resource(transitionsUri);
199 issueResource.post(jsonObject);
200 return null;
201 }
202 });
203 }
204
205 @Override
206 public void transition(final Issue issue, final TransitionInput transitionInput, final ProgressMonitor progressMonitor) {
207 if (issue.getTransitionsUri() != null) {
208 transition(issue.getTransitionsUri(), transitionInput, progressMonitor);
209 } else {
210 final UriBuilder uriBuilder = UriBuilder.fromUri(issue.getSelf());
211 uriBuilder.path("transitions");
212 transition(uriBuilder.build(), transitionInput, progressMonitor);
213 }
214 }
215
216
217 @Override
218 public void vote(final URI votesUri, ProgressMonitor progressMonitor) {
219 invoke(new Callable<Void>() {
220 @Override
221 public Void call() throws Exception {
222 final WebResource votesResource = client.resource(votesUri);
223 votesResource.post();
224 return null;
225 }
226 });
227 }
228
229 @Override
230 public void unvote(final URI votesUri, ProgressMonitor progressMonitor) {
231 invoke(new Callable<Void>() {
232 @Override
233 public Void call() throws Exception {
234 final WebResource votesResource = client.resource(votesUri);
235 votesResource.delete();
236 return null;
237 }
238 });
239
240 }
241
242 @Override
243 public void addWatcher(final URI watchersUri, @Nullable final String username, ProgressMonitor progressMonitor) {
244 invoke(new Callable<Void>() {
245 @Override
246 public Void call() throws Exception {
247 final WebResource.Builder builder = client.resource(watchersUri).type(MediaType.APPLICATION_JSON_TYPE);
248 if (username != null) {
249 builder.post(JSONObject.quote(username));
250 } else {
251 builder.post();
252 }
253 return null;
254 }
255 });
256
257 }
258
259 private String getLoggedUsername(ProgressMonitor progressMonitor) {
260 final Session session = sessionRestClient.getCurrentSession(progressMonitor);
261 return session.getUsername();
262 }
263
264 @Override
265 public void removeWatcher(final URI watchersUri, final String username, final ProgressMonitor progressMonitor) {
266 final UriBuilder uriBuilder = UriBuilder.fromUri(watchersUri);
267 if (getVersionInfo(progressMonitor).getBuildNumber() >= ServerVersionConstants.BN_JIRA_4_4) {
268 uriBuilder.queryParam("username", username);
269 } else {
270 uriBuilder.path(username).build();
271 }
272 delete(uriBuilder.build(), progressMonitor);
273 }
274
275 @Override
276 public void linkIssue(final LinkIssuesInput linkIssuesInput, final ProgressMonitor progressMonitor) {
277 final URI uri = UriBuilder.fromUri(baseUri).path("issueLink").build();
278 post(uri, new Callable<JSONObject>() {
279
280 @Override
281 public JSONObject call() throws Exception {
282 return new LinkIssuesInputGenerator(getVersionInfo(progressMonitor)).generate(linkIssuesInput);
283 }
284 }, progressMonitor);
285 }
286
287 @Override
288 public void addAttachment(ProgressMonitor progressMonitor, final URI attachmentsUri, final InputStream in, final String filename) {
289 addAttachments(progressMonitor, attachmentsUri, new AttachmentInput(filename, in));
290 }
291
292 @Override
293 public void addAttachments(ProgressMonitor progressMonitor, final URI attachmentsUri, AttachmentInput... attachments) {
294 final ArrayList<AttachmentInput> myAttachments = Lists.newArrayList(attachments);
295 invoke(new Callable<Void>() {
296 @Override
297 public Void call() throws Exception {
298 final MultiPart multiPartInput = new MultiPart();
299 for (AttachmentInput attachment : myAttachments) {
300 BodyPart bp = new BodyPart(attachment.getInputStream(), MediaType.APPLICATION_OCTET_STREAM_TYPE);
301 FormDataContentDisposition.FormDataContentDispositionBuilder dispositionBuilder =
302 FormDataContentDisposition.name(FILE_ATTACHMENT_CONTROL_NAME);
303 dispositionBuilder.fileName(attachment.getFilename());
304 final FormDataContentDisposition formDataContentDisposition = dispositionBuilder.build();
305 bp.setContentDisposition(formDataContentDisposition);
306 multiPartInput.bodyPart(bp);
307 }
308
309 postFileMultiPart(multiPartInput, attachmentsUri);
310 return null;
311 }
312
313 });
314 }
315
316 @Override
317 public InputStream getAttachment(ProgressMonitor pm, final URI attachmentUri) {
318 return invoke(new Callable<InputStream>() {
319 @Override
320 public InputStream call() throws Exception {
321 final WebResource attachmentResource = client.resource(attachmentUri);
322 return attachmentResource.get(InputStream.class);
323 }
324 });
325 }
326
327 @Override
328 public void addAttachments(ProgressMonitor progressMonitor, final URI attachmentsUri, File... files) {
329 final ArrayList<File> myFiles = Lists.newArrayList(files);
330 invoke(new Callable<Void>() {
331 @Override
332 public Void call() throws Exception {
333 final MultiPart multiPartInput = new MultiPart();
334 for (File file : myFiles) {
335 FileDataBodyPart fileDataBodyPart = new FileDataBodyPart(FILE_ATTACHMENT_CONTROL_NAME, file);
336 multiPartInput.bodyPart(fileDataBodyPart);
337 }
338 postFileMultiPart(multiPartInput, attachmentsUri);
339 return null;
340 }
341
342 });
343
344 }
345
346 private void postFileMultiPart(MultiPart multiPartInput, URI attachmentsUri) {
347 final WebResource attachmentsResource = client.resource(attachmentsUri);
348 final WebResource.Builder builder = attachmentsResource.type(MultiPartMediaTypes.createFormData());
349 builder.header("X-Atlassian-Token", "nocheck");
350 builder.post(multiPartInput);
351 }
352
353
354 @Override
355 public void watch(final URI watchersUri, ProgressMonitor progressMonitor) {
356 addWatcher(watchersUri, null, progressMonitor);
357 }
358
359 @Override
360 public void unwatch(final URI watchersUri, ProgressMonitor progressMonitor) {
361 removeWatcher(watchersUri, getLoggedUsername(progressMonitor), progressMonitor);
362 }
363
364 }