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