1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.gbif.ws.client;
15
16 import org.gbif.ws.security.Md5EncodeService;
17 import org.gbif.ws.security.Md5EncodeServiceImpl;
18 import org.gbif.ws.security.SecretKeySigningService;
19 import org.gbif.ws.security.SigningService;
20
21 import java.nio.charset.Charset;
22 import java.nio.charset.StandardCharsets;
23 import java.time.Duration;
24 import java.util.concurrent.TimeUnit;
25
26 import org.apache.http.client.HttpClient;
27 import org.apache.http.client.config.RequestConfig;
28 import org.apache.http.config.ConnectionConfig;
29 import org.apache.http.config.SocketConfig;
30 import org.apache.http.impl.client.HttpClients;
31
32 import com.fasterxml.jackson.databind.ObjectMapper;
33
34 import feign.Contract;
35 import feign.Feign;
36 import feign.InvocationHandlerFactory;
37 import feign.Request;
38 import feign.RequestInterceptor;
39 import feign.Retryer;
40 import feign.codec.Decoder;
41 import feign.codec.Encoder;
42 import feign.codec.ErrorDecoder;
43 import feign.form.spring.SpringFormEncoder;
44 import feign.httpclient.ApacheHttpClient;
45 import lombok.Builder;
46 import lombok.Data;
47
48
49
50
51
52 @SuppressWarnings("unused")
53 public class ClientBuilder {
54
55 private static final String HTTP_PROTOCOL = "http";
56 private static final String HTTPS_PROTOCOL = "https";
57
58 private String url;
59 private long connectTimeoutMillis = 10_000;
60 private long readTimeoutMillis = 60_000;
61 private RequestInterceptor requestInterceptor;
62 private Decoder decoder;
63 private Encoder encoder;
64 private ConnectionPoolConfig connectionPoolConfig;
65 private ObjectMapper objectMapper;
66 private Retryer retryer;
67 private boolean formEncoder;
68
69 private Contract contract;
70 private ErrorDecoder errorDecoder;
71 private InvocationHandlerFactory invocationHandlerFactory;
72
73
74
75
76 public ClientBuilder withExponentialBackoffRetry(
77 Duration initialInterval, double multiplier, int maxAttempts) {
78 retryer = new ClientRetryer(initialInterval.toMillis(), maxAttempts, multiplier);
79 return this;
80 }
81
82
83
84
85 public ClientBuilder withUrl(String url) {
86 this.url = url;
87 return this;
88 }
89
90
91
92
93 public ClientBuilder withCredentials(String username, String password) {
94 this.requestInterceptor = new SimpleUserAuthRequestInterceptor(username, password);
95 return this;
96 }
97
98
99
100
101 public ClientBuilder withAppKeyCredentials(String username, String appKey, String secretKey) {
102 this.requestInterceptor =
103 new GbifAuthRequestInterceptor(
104 username,
105 appKey,
106 secretKey,
107 new SecretKeySigningService(),
108 new Md5EncodeServiceImpl(objectMapper));
109 return this;
110 }
111
112
113
114
115 public ClientBuilder withConnectionPoolConfig(ConnectionPoolConfig connectionPoolConfig) {
116 this.connectionPoolConfig = connectionPoolConfig;
117 return this;
118 }
119
120
121
122
123 public ClientBuilder withConnectTimeout(int connectTimeoutMillis) {
124 this.connectTimeoutMillis = connectTimeoutMillis;
125 return this;
126 }
127
128
129
130
131 public ClientBuilder withReadTimeout(int readTimeoutMillis) {
132 this.readTimeoutMillis = readTimeoutMillis;
133 return this;
134 }
135
136
137
138
139 public ClientBuilder withObjectMapper(ObjectMapper objectMapper) {
140 this.objectMapper = objectMapper;
141 this.encoder = new ClientEncoder(objectMapper);
142 this.decoder = new ClientDecoder(objectMapper);
143 return this;
144 }
145
146
147
148
149 public ClientBuilder withCustomGbifAuth(
150 String username,
151 String appKey,
152 String secretKey,
153 SigningService signingService,
154 Md5EncodeService md5EncodeService) {
155 this.requestInterceptor =
156 new GbifAuthRequestInterceptor(
157 username, appKey, secretKey, signingService, md5EncodeService);
158 return this;
159 }
160
161 public ClientBuilder withFormEncoder() {
162 this.formEncoder = true;
163 return this;
164 }
165
166 public ClientBuilder withClientContract(ClientContract clientContract) {
167 this.contract = clientContract;
168 return this;
169 }
170
171
172
173
174 public <T> T build(Class<T> clazz) {
175 Feign.Builder builder =
176 Feign.builder()
177 .encoder(formEncoder ? new SpringFormEncoder(encoder) : encoder)
178 .decoder(decoder)
179 .errorDecoder(errorDecoder != null ? errorDecoder : new ClientErrorDecoder())
180 .contract(contract != null ? contract : ClientContract.withDefaultProcessors())
181 .options(
182 new Request.Options(
183 connectTimeoutMillis,
184 TimeUnit.MILLISECONDS,
185 readTimeoutMillis,
186 TimeUnit.MILLISECONDS,
187 true))
188 .decode404()
189 .invocationHandlerFactory(
190 invocationHandlerFactory != null
191 ? invocationHandlerFactory
192 : new ClientInvocationHandlerFactory());
193
194 if (retryer != null) {
195 builder.retryer(retryer);
196 }
197
198 if (requestInterceptor != null) {
199 builder.requestInterceptor(requestInterceptor);
200 }
201
202 if (connectionPoolConfig != null) {
203 builder.client(new ApacheHttpClient(newMultithreadedClient(connectionPoolConfig)));
204 }
205
206 return builder.target(clazz, url);
207 }
208
209
210
211
212 private static HttpClient newMultithreadedClient(ConnectionPoolConfig connectionPoolConfig) {
213
214 return HttpClients.custom()
215 .setMaxConnTotal(connectionPoolConfig.getMaxConnections())
216 .setMaxConnPerRoute(connectionPoolConfig.getMaxPerRoute())
217 .setDefaultSocketConfig(
218 SocketConfig.custom().setSoTimeout(connectionPoolConfig.getTimeout()).build())
219 .setDefaultConnectionConfig(
220 ConnectionConfig.custom()
221 .setCharset(Charset.forName(StandardCharsets.UTF_8.name()))
222 .build())
223 .setDefaultRequestConfig(
224 RequestConfig.custom()
225 .setConnectTimeout(connectionPoolConfig.getTimeout())
226 .setConnectionRequestTimeout(connectionPoolConfig.getTimeout())
227 .build())
228 .build();
229 }
230
231 @Data
232 @Builder
233 public static class ConnectionPoolConfig {
234 private final Integer timeout;
235 private final Integer maxConnections;
236 private final Integer maxPerRoute;
237 }
238 }