001 002package io.vrap.rmf.base.client; 003 004import java.io.UnsupportedEncodingException; 005import java.net.URLEncoder; 006import java.nio.charset.StandardCharsets; 007import java.time.Duration; 008import java.util.ArrayList; 009import java.util.List; 010import java.util.Map; 011import java.util.concurrent.CompletableFuture; 012import java.util.function.BiFunction; 013import java.util.function.Function; 014import java.util.stream.Collectors; 015 016import javax.annotation.Nullable; 017 018import com.fasterxml.jackson.core.type.TypeReference; 019import com.fasterxml.jackson.databind.JavaType; 020 021import org.apache.commons.lang3.builder.EqualsBuilder; 022import org.apache.commons.lang3.builder.HashCodeBuilder; 023 024public abstract class ApiMethod<T extends ApiMethod<T, TResult>, TResult> extends Base 025 implements RequestCommand<TResult>, ClientRequestCommand<TResult>, CreateHttpRequestCommand { 026 027 public static class ParamEntry<K, V> extends Base implements Map.Entry<K, V> { 028 protected final K key; 029 protected V value; 030 031 public ParamEntry(final K key) { 032 this.key = key; 033 } 034 035 public ParamEntry(final K key, final V value) { 036 this.key = key; 037 this.value = value; 038 } 039 040 @Override 041 public K getKey() { 042 return key; 043 } 044 045 @Override 046 public V getValue() { 047 return value; 048 } 049 050 @Override 051 public V setValue(final V value) { 052 V oldValue = this.value; 053 this.value = value; 054 return oldValue; 055 } 056 057 public String toUriString() { 058 try { 059 return key + "=" + URLEncoder.encode(value.toString(), StandardCharsets.UTF_8.toString()); 060 } 061 catch (UnsupportedEncodingException e) { 062 throw new EncodingException(e); 063 } 064 } 065 066 @Override 067 public boolean equals(Object o) { 068 if (this == o) 069 return true; 070 071 if (o == null || getClass() != o.getClass()) 072 return false; 073 074 ParamEntry<?, ?> that = (ParamEntry<?, ?>) o; 075 076 return new EqualsBuilder().append(key, that.key).append(value, that.value).isEquals(); 077 } 078 079 @Override 080 public int hashCode() { 081 return new HashCodeBuilder(17, 37).append(key).append(value).toHashCode(); 082 } 083 } 084 085 private Function<ApiHttpRequest, ApiHttpRequest> httpRequestDecorator = Function.identity(); 086 private ApiHttpHeaders headers = new ApiHttpHeaders(); 087 private List<ParamEntry<String, String>> queryParams = new ArrayList<>(); 088 private final ApiHttpClient apiHttpClient; 089 090 protected ApiHttpClient apiHttpClient() { 091 return apiHttpClient; 092 } 093 094 public ApiMethod(final ApiHttpClient apiHttpClient) { 095 this.apiHttpClient = apiHttpClient; 096 } 097 098 public ApiMethod(final ApiHttpClient apiHttpClient, ApiHttpHeaders headers, 099 List<ParamEntry<String, String>> queryParams) { 100 this.apiHttpClient = apiHttpClient; 101 this.headers = headers; 102 this.queryParams = new ArrayList<>(queryParams); 103 } 104 105 public ApiMethod(final ApiMethod<T, TResult> apiMethod) { 106 this.apiHttpClient = apiMethod.apiHttpClient; 107 this.headers = new ApiHttpHeaders(apiMethod.headers); 108 this.queryParams = new ArrayList<>(apiMethod.queryParams); 109 this.httpRequestDecorator = apiMethod.httpRequestDecorator; 110 } 111 112 /** 113 * adds a header with the specified value 114 * @param key header name 115 * @param value header value 116 * @return T 117 */ 118 public T addHeader(final String key, final String value) { 119 final T c = copy(); 120 ((ApiMethod<T, TResult>) c).headers = ((ApiMethod<T, TResult>) c).headers.addHeader(key, value); 121 return c; 122 } 123 124 /** 125 * removes the specified header 126 * @param key header name 127 * @return T 128 */ 129 public T withoutHeader(final String key) { 130 final T c = copy(); 131 ((ApiMethod<T, TResult>) c).headers = ((ApiMethod<T, TResult>) c).headers.withoutHeader(key); 132 return c; 133 } 134 135 /** 136 * set the header with the specified value 137 * @param key header name 138 * @param value header value 139 * @return T 140 */ 141 public T withHeader(final String key, final String value) { 142 final T c = copy(); 143 ((ApiMethod<T, TResult>) c).headers = ((ApiMethod<T, TResult>) c).headers.withHeader(key, value); 144 return c; 145 } 146 147 /** 148 * set the headers 149 * @param headers 150 * @return 151 */ 152 public T withHeaders(final ApiHttpHeaders headers) { 153 final T c = copy(); 154 ((ApiMethod<T, TResult>) c).headers = ((ApiMethod<T, TResult>) c).headers = headers; 155 return c; 156 } 157 158 /** 159 * set specific content type 160 * @param contentType 161 * @return 162 */ 163 public T contentType(final String contentType) { 164 final T c = copy(); 165 ((ApiMethod<T, TResult>) c).headers = ((ApiMethod<T, TResult>) c).headers 166 .withHeader(ApiHttpHeaders.CONTENT_TYPE, contentType); 167 168 return c; 169 } 170 171 public ApiHttpHeaders getHeaders() { 172 return this.headers; 173 } 174 175 /** 176 * add an additional query parameter 177 * @param key query parameter name 178 * @param value query parameter value 179 * @param <V> value type 180 * @return T 181 */ 182 public <V> T addQueryParam(final String key, final V value) { 183 final T c = copy(); 184 ((ApiMethod<T, TResult>) c).queryParams.add(new ParamEntry<>(key, value.toString())); 185 return c; 186 } 187 188 /** 189 * set the query parameter with the specified value 190 * @param key query parameter name 191 * @param value query parameter value 192 * @param <V> value type 193 * @return T 194 */ 195 public <V> T withQueryParam(final String key, final V value) { 196 return withoutQueryParam(key).addQueryParam(key, value); 197 } 198 199 /** 200 * removes the specified query parameter 201 * @param key query parameter name 202 * @return T 203 */ 204 public T withoutQueryParam(final String key) { 205 final T c = copy(); 206 ((ApiMethod<T, TResult>) c).queryParams = ((ApiMethod<T, TResult>) c).queryParams.stream() 207 .filter(e -> !e.getKey().equalsIgnoreCase(key)) 208 .collect(Collectors.toList()); 209 return c; 210 } 211 212 /** 213 * set the query parameters 214 * @param queryParams list of query parameters 215 * @return T 216 */ 217 public T withQueryParams(final List<ParamEntry<String, String>> queryParams) { 218 final T c = copy(); 219 ((ApiMethod<T, TResult>) c).queryParams = queryParams; 220 return c; 221 } 222 223 /** 224 * add the query parameters 225 * @param queryParams list of query parameters 226 * @return T 227 */ 228 public T addQueryParams(final List<ParamEntry<String, String>> queryParams) { 229 final T c = copy(); 230 231 ((ApiMethod<T, TResult>) c).queryParams.addAll(queryParams); 232 return c; 233 } 234 235 public List<ParamEntry<String, String>> getQueryParams() { 236 return new ArrayList<>(this.queryParams); 237 } 238 239 public List<String> getQueryParam(final String key) { 240 return this.queryParams.stream().filter(e -> e.key.equals(key)).map(e -> e.value).collect(Collectors.toList()); 241 } 242 243 public List<String> getQueryParamUriStrings() { 244 return this.queryParams.stream().map(ParamEntry::toUriString).collect(Collectors.toList()); 245 } 246 247 public String getQueryParamUriString() { 248 return this.queryParams.stream().map(ParamEntry::toUriString).collect(Collectors.joining("&")); 249 } 250 251 @Nullable 252 public String getFirstQueryParam(final String key) { 253 return this.queryParams.stream() 254 .filter(e -> e.getKey().equals(key)) 255 .map(Map.Entry::getValue) 256 .findFirst() 257 .orElse(null); 258 } 259 260 protected abstract T copy(); 261 262 protected abstract ApiHttpRequest buildHttpRequest(); 263 264 public ApiHttpRequest createHttpRequest() { 265 return httpRequestDecorator.apply(this.buildHttpRequest()); 266 } 267 268 public CompletableFuture<ApiHttpResponse<TResult>> execute() { 269 return execute(apiHttpClient()); 270 } 271 272 public abstract CompletableFuture<ApiHttpResponse<TResult>> execute(final ApiHttpClient client); 273 274 /** 275 * allows to modify the HTTP request before it will be executed 276 * @param op decorator function 277 * @return the method itself 278 */ 279 public T withHttpRequest(Function<ApiHttpRequest, ApiHttpRequest> op) { 280 final T c = copy(); 281 ((ApiMethod<T, TResult>) c).httpRequestDecorator = httpRequestDecorator.andThen(op); 282 return c; 283 } 284 285 /** 286 * allows to provide a function to modify the ApiMethod itself 287 * @param op decorator function 288 * @return the method itself 289 */ 290 public T with(Function<T, T> op) { 291 final T c = copy(); 292 return op.apply(c); 293 } 294 295 /** 296 * allows to provide a function to modify the ApiMethod itself 297 * @param op decorator function 298 * @param arg decorator function argument 299 * @param <U> the method type itself 300 * @return the method itself 301 */ 302 public <U> T with(BiFunction<T, U, T> op, U arg) { 303 final T c = copy(); 304 return op.apply(c, arg); 305 } 306 307 public <TReturn> CompletableFuture<ApiHttpResponse<TReturn>> execute(final Class<TReturn> returnType) { 308 return execute(apiHttpClient(), returnType); 309 } 310 311 public <TReturn> CompletableFuture<ApiHttpResponse<TReturn>> execute(final ApiHttpClient client, 312 final Class<TReturn> returnType) { 313 return client.execute(this, returnType).toCompletableFuture(); 314 } 315 316 public <TReturn> CompletableFuture<ApiHttpResponse<TReturn>> execute( 317 final TypeReference<TReturn> returnTypeReference) { 318 return execute(apiHttpClient(), returnTypeReference); 319 } 320 321 public <TReturn> CompletableFuture<ApiHttpResponse<TReturn>> execute(final ApiHttpClient client, 322 final TypeReference<TReturn> returnTypeReference) { 323 return client.execute(this, returnTypeReference).toCompletableFuture(); 324 } 325 326 public <TReturn> CompletableFuture<ApiHttpResponse<TReturn>> execute(final JavaType returnJavaType) { 327 return execute(apiHttpClient(), returnJavaType); 328 } 329 330 public <TReturn> CompletableFuture<ApiHttpResponse<TReturn>> execute(final ApiHttpClient client, 331 final JavaType returnJavaType) { 332 return client.execute(this, returnJavaType); 333 } 334 335 public ApiHttpResponse<TResult> executeBlocking(final Duration timeout) { 336 return executeBlocking(apiHttpClient(), timeout); 337 } 338 339 public abstract ApiHttpResponse<TResult> executeBlocking(final ApiHttpClient client, final Duration timeout); 340 341 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final Class<TReturn> clazz) { 342 return executeBlocking(apiHttpClient(), ApiHttpClient.DEFAULT_TIMEOUT, clazz); 343 }; 344 345 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final ApiHttpClient client, final Class<TReturn> clazz) { 346 return executeBlocking(client, ApiHttpClient.DEFAULT_TIMEOUT, clazz); 347 }; 348 349 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final Duration timeout, final Class<TReturn> clazz) { 350 return executeBlocking(apiHttpClient(), timeout, clazz); 351 } 352 353 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final ApiHttpClient client, final Duration timeout, 354 final Class<TReturn> clazz) { 355 return client.executeBlocking(this, clazz, timeout); 356 } 357 358 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final TypeReference<TReturn> typeReference) { 359 return executeBlocking(apiHttpClient(), ApiHttpClient.DEFAULT_TIMEOUT, typeReference); 360 } 361 362 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final ApiHttpClient client, 363 final TypeReference<TReturn> typeReference) { 364 return executeBlocking(client, ApiHttpClient.DEFAULT_TIMEOUT, typeReference); 365 } 366 367 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final Duration timeout, 368 final TypeReference<TReturn> typeReference) { 369 return executeBlocking(apiHttpClient(), timeout, typeReference); 370 } 371 372 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final ApiHttpClient client, final Duration timeout, 373 TypeReference<TReturn> typeReference) { 374 return client.executeBlocking(this, typeReference, timeout); 375 } 376 377 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final JavaType javaType) { 378 return executeBlocking(apiHttpClient(), ApiHttpClient.DEFAULT_TIMEOUT, javaType); 379 } 380 381 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final ApiHttpClient client, final JavaType javaType) { 382 return executeBlocking(apiHttpClient(), ApiHttpClient.DEFAULT_TIMEOUT, javaType); 383 } 384 385 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final Duration timeout, final JavaType javaType) { 386 return executeBlocking(apiHttpClient(), timeout, javaType); 387 } 388 389 public <TReturn> ApiHttpResponse<TReturn> executeBlocking(final ApiHttpClient client, final Duration timeout, 390 JavaType javaType) { 391 return client.executeBlocking(this, javaType, timeout); 392 } 393 394 public CompletableFuture<ApiHttpResponse<byte[]>> send() { 395 return apiHttpClient.send(this); 396 } 397 398 public ApiHttpResponse<byte[]> sendBlocking() { 399 return sendBlocking(ApiHttpClient.DEFAULT_TIMEOUT); 400 } 401 402 public ApiHttpResponse<byte[]> sendBlocking(final Duration timeout) { 403 return apiHttpClient.sendBlocking(this, timeout); 404 } 405}