1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.atlassian.jira.rest.client.internal.async;
17
18 import com.atlassian.jira.rest.client.api.domain.util.ErrorCollection;
19 import com.atlassian.jira.rest.client.internal.json.JsonArrayParser;
20 import com.atlassian.jira.rest.client.internal.json.JsonObjectParser;
21 import com.atlassian.jira.rest.client.internal.json.JsonParseUtil;
22 import com.atlassian.jira.rest.client.internal.json.JsonParser;
23 import com.atlassian.httpclient.api.EntityBuilder;
24 import com.atlassian.httpclient.api.HttpClient;
25 import com.atlassian.httpclient.api.Response;
26 import com.atlassian.httpclient.api.ResponsePromise;
27 import com.atlassian.jira.rest.client.api.RestClientException;
28 import com.atlassian.jira.rest.client.internal.json.gen.JsonGenerator;
29 import com.atlassian.util.concurrent.Promise;
30 import com.google.common.base.Function;
31 import com.google.common.collect.ImmutableList;
32 import org.apache.commons.lang.StringUtils;
33 import org.codehaus.jettison.json.JSONArray;
34 import org.codehaus.jettison.json.JSONException;
35 import org.codehaus.jettison.json.JSONObject;
36
37 import javax.annotation.Nullable;
38 import java.io.ByteArrayInputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.net.URI;
42 import java.nio.charset.Charset;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.Map;
46
47
48
49
50
51
52 public abstract class AbstractAsynchronousRestClient {
53
54 private static final String JSON_CONTENT_TYPE = "application/json";
55
56 private final HttpClient client;
57
58 protected AbstractAsynchronousRestClient(HttpClient client) {
59 this.client = client;
60 }
61
62 protected interface ResponseHandler<T> {
63 T handle(Response request) throws JSONException, IOException;
64 }
65
66 protected final <T> Promise<T> getAndParse(final URI uri, final JsonParser<?, T> parser) {
67 return callAndParse(client.newRequest(uri).setAccept("application/json").get(), parser);
68 }
69
70 protected final <I, T> Promise<T> postAndParse(final URI uri, I entity, final JsonGenerator<I> jsonGenerator,
71 final JsonObjectParser<T> parser) {
72 final ResponsePromise responsePromise = client.newRequest(uri)
73 .setEntity(toEntity(jsonGenerator, entity))
74 .post();
75 return callAndParse(responsePromise, parser);
76 }
77
78 protected final <T> Promise<T> postAndParse(final URI uri, final JSONObject entity, final JsonObjectParser<T> parser) {
79 final ResponsePromise responsePromise = client.newRequest(uri)
80 .setEntity(entity.toString())
81 .setContentType(JSON_CONTENT_TYPE)
82 .post();
83 return callAndParse(responsePromise, parser);
84 }
85
86 protected final Promise<Void> post(final URI uri, final String entity) {
87 final ResponsePromise responsePromise = client.newRequest(uri)
88 .setEntity(entity)
89 .setContentType(JSON_CONTENT_TYPE)
90 .post();
91 return call(responsePromise);
92 }
93
94 protected final Promise<Void> post(final URI uri, final JSONObject entity) {
95 return post(uri, entity.toString());
96 }
97
98 protected final <T> Promise<Void> post(final URI uri, final T entity, final JsonGenerator<T> jsonGenerator) {
99 final ResponsePromise responsePromise = client.newRequest(uri)
100 .setEntity(toEntity(jsonGenerator, entity))
101 .post();
102 return call(responsePromise);
103 }
104
105 protected final Promise<Void> post(final URI uri) {
106 return post(uri, StringUtils.EMPTY);
107 }
108
109 protected final <I, T> Promise<T> putAndParse(final URI uri, I entity, final JsonGenerator<I> jsonGenerator,
110 final JsonObjectParser<T> parser) {
111 final ResponsePromise responsePromise = client.newRequest(uri)
112 .setEntity(toEntity(jsonGenerator, entity))
113 .put();
114 return callAndParse(responsePromise, parser);
115 }
116
117 protected final Promise<Void> delete(final URI uri) {
118 final ResponsePromise responsePromise = client.newRequest(uri).delete();
119 return call(responsePromise);
120 }
121
122 protected final <T> Promise<T> callAndParse(final ResponsePromise responsePromise, final ResponseHandler<T> responseHandler) {
123 final Function<Response, ? extends T> transformFunction = toFunction(responseHandler);
124
125 return new DelegatingPromise<T>(responsePromise.<T>transform()
126 .ok(transformFunction)
127 .created(transformFunction)
128 .others(AbstractAsynchronousRestClient.<T>errorFunction())
129 .toPromise());
130 }
131
132 @SuppressWarnings("unchecked")
133 protected final <T> Promise<T> callAndParse(final ResponsePromise responsePromise, final JsonParser<?, T> parser) {
134 final ResponseHandler<T> responseHandler = new ResponseHandler<T>() {
135 @Override
136 public T handle(Response response) throws JSONException, IOException {
137 final String body = response.getEntity();
138 return (T) (parser instanceof JsonObjectParser ?
139 ((JsonObjectParser) parser).parse(new JSONObject(body)) :
140 ((JsonArrayParser) parser).parse(new JSONArray(body)));
141 }
142 };
143 return callAndParse(responsePromise, responseHandler);
144 }
145
146 protected final Promise<Void> call(final ResponsePromise responsePromise) {
147 return new DelegatingPromise<Void>(responsePromise.<Void>transform()
148 .ok(constant((Void) null))
149 .created(constant((Void) null))
150 .noContent(constant((Void) null))
151 .others(AbstractAsynchronousRestClient.<Void>errorFunction())
152 .toPromise());
153 }
154
155 protected HttpClient client() {
156 return client;
157 }
158
159 private static <T> Function<Response, T> errorFunction() {
160 return new Function<Response, T>() {
161 @Override
162 public T apply(Response response) {
163 try {
164 final String body = response.getEntity();
165 final Collection<ErrorCollection> errorMessages = extractErrors(response.getStatusCode(), body);
166 throw new RestClientException(errorMessages, response.getStatusCode());
167 } catch (JSONException e) {
168 throw new RestClientException(e, response.getStatusCode());
169 }
170 }
171 };
172 }
173
174 private static <T> Function<Response, ? extends T> toFunction(final ResponseHandler<T> responseHandler) {
175 return new Function<Response, T>() {
176 @Override
177 public T apply(@Nullable Response input) {
178 try {
179 return responseHandler.handle(input);
180 } catch (JSONException e) {
181 throw new RestClientException(e);
182 } catch (IOException e) {
183 throw new RestClientException(e);
184 }
185 }
186 };
187 }
188
189 private static <T> Function<Response, T> constant(final T value) {
190 return new Function<Response, T>() {
191 @Override
192 public T apply(Response input) {
193 return value;
194 }
195 };
196 }
197
198 static Collection<ErrorCollection> extractErrors(final int status, final String body) throws JSONException {
199 if (body == null) {
200 return Collections.emptyList();
201 }
202 final JSONObject jsonObject = new JSONObject(body);
203 final JSONArray issues = jsonObject.optJSONArray("issues");
204 final ImmutableList.Builder<ErrorCollection> results = ImmutableList.builder();
205 if (issues != null && issues.length() == 0) {
206 final JSONArray errors = jsonObject.optJSONArray("errors");
207 for (int i = 0; i < errors.length(); i++) {
208 final JSONObject currentJsonObject = errors.getJSONObject(i);
209 results.add(getErrorsFromJson(currentJsonObject.getInt("status"), currentJsonObject
210 .optJSONObject("elementErrors")));
211 }
212 } else {
213 results.add(getErrorsFromJson(status, jsonObject));
214 }
215 return results.build();
216 }
217
218 private static ErrorCollection getErrorsFromJson(final int status, final JSONObject jsonObject) throws JSONException {
219 final JSONObject jsonErrors = jsonObject.optJSONObject("errors");
220 final JSONArray jsonErrorMessages = jsonObject.optJSONArray("errorMessages");
221
222 final Collection<String> errorMessages;
223 if (jsonErrorMessages != null) {
224 errorMessages = JsonParseUtil.toStringCollection(jsonErrorMessages);
225 } else {
226 errorMessages = Collections.emptyList();
227 }
228
229 final Map<String, String> errors;
230 if (jsonErrors != null && jsonErrors.length() > 0) {
231 errors = JsonParseUtil.toStringMap(jsonErrors.names(), jsonErrors);
232 } else {
233 errors = Collections.emptyMap();
234 }
235 return new ErrorCollection(status, errorMessages, errors);
236 }
237
238 private <T> EntityBuilder toEntity(final JsonGenerator<T> generator, final T bean) {
239 return new EntityBuilder() {
240
241 @Override
242 public Entity build() {
243 return new Entity() {
244 @Override
245 public Map<String, String> getHeaders() {
246 return Collections.singletonMap("Content-Type", JSON_CONTENT_TYPE);
247 }
248
249 @Override
250 public InputStream getInputStream() {
251 try {
252 return new ByteArrayInputStream(generator.generate(bean).toString().getBytes(Charset
253 .forName("UTF-8")));
254 } catch (JSONException e) {
255 throw new RestClientException(e);
256 }
257 }
258 };
259 }
260 };
261 }
262 }