forked from gooddata/gooddata-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAbstractService.java
More file actions
187 lines (157 loc) · 6.42 KB
/
AbstractService.java
File metadata and controls
187 lines (157 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*
* Copyright (C) 2007-2014, GoodData(R) Corporation. All rights reserved.
*/
package com.gooddata;
import static com.gooddata.util.Validate.notNull;
import static java.lang.String.format;
import static org.springframework.http.HttpMethod.GET;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.client.HttpMessageConverterExtractor;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.TimeUnit;
/**
* Parent for GoodData services providing helpers for REST API calls and polling.
*/
public abstract class AbstractService {
public static final Integer WAIT_BEFORE_RETRY_IN_MILLIS = 5 * 1000;
protected final RestTemplate restTemplate;
protected final ObjectMapper mapper = new ObjectMapper();
protected final RequestCallback noopRequestCallback = new RequestCallback() {
@Override
public void doWithRequest(final ClientHttpRequest request) throws IOException {
}
};
private final ResponseExtractor<ClientHttpResponse> reusableResponseExtractor = new ResponseExtractor<ClientHttpResponse>() {
@Override
public ClientHttpResponse extractData(final ClientHttpResponse response) throws IOException {
return new ReusableClientHttpResponse(response);
}
};
/**
* Sets RESTful HTTP Spring template. Should be called from constructor of concrete service extending
* this abstract one.
*
* @param restTemplate RESTful HTTP Spring template
*/
public AbstractService(RestTemplate restTemplate) {
this.restTemplate = notNull(restTemplate, "restTemplate");
}
final <R> R poll(final PollHandler<?,R> handler, long timeout, final TimeUnit unit) {
notNull(handler, "handler");
final long start = System.currentTimeMillis();
while (true) {
if (pollOnce(handler)) {
return handler.getResult();
}
if (unit != null && start + unit.toMillis(timeout) < System.currentTimeMillis()) {
throw new GoodDataException("timeout");
}
try {
Thread.sleep(WAIT_BEFORE_RETRY_IN_MILLIS);
} catch (InterruptedException e) {
throw new GoodDataException("interrupted");
}
}
}
final <P> boolean pollOnce(final PollHandler<P,?> handler) {
notNull(handler, "handler");
final ClientHttpResponse response;
try {
response = restTemplate.execute(handler.getPollingUri(), GET, noopRequestCallback, reusableResponseExtractor);
} catch (GoodDataRestException e) {
handler.handlePollException(e);
throw new GoodDataException("Handler " + handler.getClass().getName() + " didn't handle exception", e);
}
try {
if (handler.isFinished(response)) {
final P data = extractData(response, handler.getPollClass());
handler.handlePollResult(data);
} else if (HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())) {
throw new GoodDataException(
format("Polling returned client error HTTP status %s", response.getStatusCode().value())
);
}
} catch (IOException e) {
throw new GoodDataException("I/O error occurred during HTTP response extraction", e);
}
return handler.isDone();
}
protected final <T> T extractData(ClientHttpResponse response, Class<T> cls) throws IOException {
notNull(response, "response");
notNull(cls, "cls");
if (Void.class.isAssignableFrom(cls)) {
return null;
}
return new HttpMessageConverterExtractor<>(cls, restTemplate.getMessageConverters()).extractData(response);
}
private static class ReusableClientHttpResponse implements ClientHttpResponse {
private byte[] body;
private final HttpStatus statusCode;
private final int rawStatusCode;
private final String statusText;
private final HttpHeaders headers;
public ReusableClientHttpResponse(ClientHttpResponse response) {
try {
final InputStream bodyStream = response.getBody();
if (bodyStream != null) {
body = FileCopyUtils.copyToByteArray(bodyStream);
}
statusCode = response.getStatusCode();
rawStatusCode = response.getRawStatusCode();
statusText = response.getStatusText();
headers = response.getHeaders();
} catch (IOException e) {
throw new RuntimeException("Unable to read from HTTP response", e);
} finally {
if (response != null) {
response.close();
}
}
}
@Override
public HttpStatus getStatusCode() throws IOException {
return statusCode;
}
@Override
public int getRawStatusCode() throws IOException {
return rawStatusCode;
}
@Override
public String getStatusText() throws IOException {
return statusText;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public InputStream getBody() throws IOException {
return body != null ? new ByteArrayInputStream(body) : null;
}
@Override
public void close() {
//already closed
}
}
protected static class OutputStreamResponseExtractor implements ResponseExtractor<Integer> {
private final OutputStream output;
public OutputStreamResponseExtractor(OutputStream output) {
this.output = output;
}
@Override
public Integer extractData(ClientHttpResponse response) throws IOException {
return FileCopyUtils.copy(response.getBody(), output);
}
}
}