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