<commit_before>/*
 * Copyright (C) 2016 Cognifide Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.knotx.adapter.common.http;

import io.knotx.adapter.common.exception.AdapterServiceContractException;
import io.knotx.adapter.common.exception.UnsupportedServiceException;
import io.knotx.adapter.common.placeholders.UriTransformer;
import io.knotx.dataobjects.AdapterRequest;
import io.knotx.dataobjects.ClientRequest;
import io.knotx.dataobjects.ClientResponse;
import io.knotx.http.AllowedHeadersFilter;
import io.knotx.http.MultiMapCollector;
import io.knotx.util.DataObjectsUtil;
import io.reactivex.Single;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.reactivex.core.MultiMap;
import io.vertx.reactivex.core.buffer.Buffer;
import io.vertx.reactivex.ext.web.client.HttpRequest;
import io.vertx.reactivex.ext.web.client.HttpResponse;
import io.vertx.reactivex.ext.web.client.WebClient;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import org.apache.commons.lang3.tuple.Pair;

public class HttpClientFacade {

  private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientFacade.class);
  private static final String PATH_PROPERTY_KEY = "path";
  private static final String QUERY_PARAMS_PROPERTY_KEY = "queryParams";
  private static final String HEADERS_PROPERTY_KEY = "headers";

  private final List<ServiceMetadata> services;

  private final WebClient webClient;

  private final JsonObject customRequestHeader;

  public HttpClientFacade(WebClient webClient, HttpAdapterConfiguration configuration) {
    this.webClient = webClient;
    this.services = configuration.getServices();
    this.customRequestHeader = configuration.getCustomRequestHeader();
  }

  public Single<ClientResponse> process(AdapterRequest message, HttpMethod method) {
    return Single.just(message)
        .doOnSuccess(this::validateContract)
        .map(this::prepareRequestData)
        .flatMap(
            serviceRequest -> callService(serviceRequest, method)
                .doOnSuccess(resp -> logResponse(serviceRequest, resp)))
        .flatMap(this::wrapResponse);
  }

  private void logResponse(Pair<ClientRequest, ServiceMetadata> request,
      HttpResponse<Buffer> resp) {
    if (resp.statusCode() >= 400 && resp.statusCode() < 600) {
      LOGGER.error("{} {} -> Got response {}, headers[{}]",
          logResponseData(request, resp));
    } else if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("{} {} -> Got response {}, headers[{}]",
          logResponseData(request, resp));
    }
  }

  private Object[] logResponseData(Pair<ClientRequest, ServiceMetadata> request,
      HttpResponse<Buffer> resp) {
    Object[] data = {
        request.getLeft().getMethod(),
        toUrl(request),
        resp.statusCode(),
        DataObjectsUtil.toString(resp.headers())};

    return data;
  }

  private String toUrl(Pair<ClientRequest, ServiceMetadata> request) {
    return new StringBuilder(request.getRight().getDomain()).append(request.getRight().getPort())
        .append(request.getLeft().getPath()).toString();
  }

  /**
   * Method to validate contract or params JsonObject for the AdapterProxy Service<br> The contract
   * checks if all required fields exists in the object. throwing AdapterServiceContractException in
   * case of contract violation.<br>
   *
   * @param message - Event Bus Json Object message that contains 'clientRequest' and 'params'
   * objects.
   */
  protected void validateContract(AdapterRequest message) {
    if (message.getParams() == null || !message.getParams().containsKey(PATH_PROPERTY_KEY)) {
      throw new AdapterServiceContractException("Parameter `path` was not defined in `params`!");
    }
  }

  /**
   * Method responsible for building request to the service. <br> <br> The responsibility of the
   * method is to build ClientRequest based on the original Http Request<br> - It must set path
   * property of the request based on the params<br> - It might set headers of the request if
   * needed.<br> <br> In case of headers created modified in this method, ensure that your service
   * configuration allows passing those headers to the target service. See 'allowedRequestHeaders'
   * section of the configuration <br>
   *
   * @param originalRequest - ClientRequest representing original request comming to the Knot.x
   * @param params - JsonObject of the params to be used to build request.
   * @return ClientRequest representing Http request to the target service
   */
  protected ClientRequest buildServiceRequest(ClientRequest originalRequest, JsonObject params) {
    return new ClientRequest(originalRequest)
        .setPath(UriTransformer
            .resolveServicePath(params.getString(PATH_PROPERTY_KEY), originalRequest));
  }

  private Pair<ClientRequest, ServiceMetadata> prepareRequestData(AdapterRequest adapterRequest) {
    final Pair<ClientRequest, ServiceMetadata> serviceData;

    final JsonObject params = adapterRequest.getParams();
    final ClientRequest serviceRequest = buildServiceRequest(adapterRequest.getRequest(), params);
    final Optional<ServiceMetadata> serviceMetadata = findServiceMetadata(serviceRequest.getPath());

    if (serviceMetadata.isPresent()) {
      final ServiceMetadata metadata = serviceMetadata.get();<commit_msg>Revert javadocs formatting<commit_after>/*
 * Copyright (C) 2016 Cognifide Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.knotx.adapter.common.http;

import io.knotx.adapter.common.exception.AdapterServiceContractException;
import io.knotx.adapter.common.exception.UnsupportedServiceException;
import io.knotx.adapter.common.placeholders.UriTransformer;
import io.knotx.dataobjects.AdapterRequest;
import io.knotx.dataobjects.ClientRequest;
import io.knotx.dataobjects.ClientResponse;
import io.knotx.http.AllowedHeadersFilter;
import io.knotx.http.MultiMapCollector;
import io.knotx.util.DataObjectsUtil;
import io.reactivex.Single;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.reactivex.core.MultiMap;
import io.vertx.reactivex.core.buffer.Buffer;
import io.vertx.reactivex.ext.web.client.HttpRequest;
import io.vertx.reactivex.ext.web.client.HttpResponse;
import io.vertx.reactivex.ext.web.client.WebClient;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import org.apache.commons.lang3.tuple.Pair;

public class HttpClientFacade {

  private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientFacade.class);
  private static final String PATH_PROPERTY_KEY = "path";
  private static final String QUERY_PARAMS_PROPERTY_KEY = "queryParams";
  private static final String HEADERS_PROPERTY_KEY = "headers";

  private final List<ServiceMetadata> services;

  private final WebClient webClient;

  private final JsonObject customRequestHeader;

  public HttpClientFacade(WebClient webClient, HttpAdapterConfiguration configuration) {
    this.webClient = webClient;
    this.services = configuration.getServices();
    this.customRequestHeader = configuration.getCustomRequestHeader();
  }

  public Single<ClientResponse> process(AdapterRequest message, HttpMethod method) {
    return Single.just(message)
        .doOnSuccess(this::validateContract)
        .map(this::prepareRequestData)
        .flatMap(
            serviceRequest -> callService(serviceRequest, method)
                .doOnSuccess(resp -> logResponse(serviceRequest, resp)))
        .flatMap(this::wrapResponse);
  }

  private void logResponse(Pair<ClientRequest, ServiceMetadata> request,
      HttpResponse<Buffer> resp) {
    if (resp.statusCode() >= 400 && resp.statusCode() < 600) {
      LOGGER.error("{} {} -> Got response {}, headers[{}]",
          logResponseData(request, resp));
    } else if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("{} {} -> Got response {}, headers[{}]",
          logResponseData(request, resp));
    }
  }

  private Object[] logResponseData(Pair<ClientRequest, ServiceMetadata> request,
      HttpResponse<Buffer> resp) {
    Object[] data = {
        request.getLeft().getMethod(),
        toUrl(request),
        resp.statusCode(),
        DataObjectsUtil.toString(resp.headers())};

    return data;
  }

  private String toUrl(Pair<ClientRequest, ServiceMetadata> request) {
    return new StringBuilder(request.getRight().getDomain()).append(request.getRight().getPort())
        .append(request.getLeft().getPath()).toString();
  }

  /**
   * Method to validate contract or params JsonObject for the AdapterProxy Service<br>
   * The contract checks if all required fields exists in the object.
   * throwing AdapterServiceContractException in case of contract violation.<br>
   *
   * @param message - Event Bus Json Object message that contains 'clientRequest' and 'params'
   * objects.
   */
  protected void validateContract(AdapterRequest message) {
    if (message.getParams() == null || !message.getParams().containsKey(PATH_PROPERTY_KEY)) {
      throw new AdapterServiceContractException("Parameter `path` was not defined in `params`!");
    }
  }

  /**
   * Method responsible for building request to the service.
   * <br>
   * <br>
   * The responsibility of the method is to build ClientRequest based on the original Http
   * Request<br>
   * - It must set path property of the request based on the params<br>
   * - It might set headers of the request if needed.<br>
   * <br>
   * In case of headers created modified in this method, ensure that your service configuration
   * allows passing those headers to the target service. See 'allowedRequestHeaders' section
   * of the configuration <br>
   *
   * @param originalRequest - ClientRequest representing original request comming to the Knot.x
   * @param params - JsonObject of the params to be used to build request.
   * @return ClientRequest representing Http request to the target service
   */
  protected ClientRequest buildServiceRequest(ClientRequest originalRequest, JsonObject params) {
    return new ClientRequest(originalRequest)
        .setPath(UriTransformer
            .resolveServicePath(params.getString(PATH_PROPERTY_KEY), originalRequest));
  }

  private Pair<ClientRequest, ServiceMetadata> prepareRequestData(AdapterRequest adapterRequest) {
    final Pair<ClientRequest, ServiceMetadata> serviceData;

    final JsonObject params = adapterRequest.getParams();
    final ClientRequest serviceRequest = buildServiceRequest(adapterRequest.getRequest(), params);
    final Optional<ServiceMetadata> serviceMetadata = findServiceMetadata(serviceRequest.getPath());

    if (serviceMetadata.isPresent()) {
      final ServiceMetadata metadata = serviceMetadata.get();
{'fraction_non_alphanumeric': 0.09504270641433596, 'fraction_numerical': 0.002512142019762184, 'mean_word_length': 4.367640449438202, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5112188', 'n_tokens_mistral': 3203, 'n_tokens_neox': 3147, 'n_words': 915}