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