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