All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sap.cloud.rest.api.client.RestApiClient Maven / Gradle / Ivy

package com.sap.cloud.rest.api.client;

import static com.sap.cloud.rest.api.client.utils.ValidateArgument.isNotNull;
import static java.text.MessageFormat.format;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sap.cloud.rest.api.client.config.RestApiClientConfig;
import com.sap.cloud.rest.api.client.exceptions.ConnectionException;
import com.sap.cloud.rest.api.client.exceptions.ResponseException;
import com.sap.cloud.rest.api.client.handler.DefaultResponseHandler;
import com.sap.cloud.rest.api.client.handler.DefaultStatusCodeHandler;
import com.sap.cloud.rest.api.client.handler.StatusCodeHandler;
import com.sap.cloud.rest.api.client.http.HttpClientProvider;
import com.sap.cloud.rest.api.client.http.HttpClientProviderFactory;
import com.sap.cloud.rest.api.client.model.HttpExchangeContext;
import com.sap.cloud.rest.api.client.model.Request;
import com.sap.cloud.rest.api.client.model.Response;

/**
 * A generic Java client for Rest API calls. Capable of executing a
 * {@link Request} and returning a {@link Response}. Requires an
 * {@link RestApiClientConfig} to construct. Provided with utility methods for
 * building the needed {@link URI} and {@link HttpEntity}.
 */
public abstract class RestApiClient {

    static final String IO_EXCEPTION_WHILE_HANDLING_RESPONSE_MSG = "IOException occurred while handling response. Context: [{0}].";
    static final String IO_EXCEPTION_WHILE_EXECUTING_REQUEST_MSG = "IOException occurred while executing request. Request: [{0}].";
    static final String PATH_NOT_VALID_MSG = "The given path is not a valid URI.";
    static final String HOST_NOT_VALID_MSG = "Host [{0}] is not a valid URI.";

    static final String HTTP_CLIENT_PROVIDER_DISPLAY_NAME = "HTTP client provider";
    static final String CONFIG_DISPLAY_NAME = "Configuration";

    private final HttpClient httpClient;
    private final URL host;

    /**
     * Creates a {@link RestApiClient} instance with the given configuration.
     *
     * @param restApiClientConfig the configuration to be used.
     */
    protected RestApiClient(RestApiClientConfig restApiClientConfig) {
        isNotNull(CONFIG_DISPLAY_NAME, restApiClientConfig);
        HttpClientProvider httpClientProvider = HttpClientProviderFactory
                .createHttpClientProvider(restApiClientConfig);

        this.httpClient = httpClientProvider.createHttpClient(restApiClientConfig.getRoutePlanner());
        this.host = getHostAsURL(restApiClientConfig.getHost());
    }

    /**
     * A constructor which allows providing a custom {@link HttpClientProvider}.
     *
     * @param restApiClientConfig
     *            the configuration to be used.
     * @param httpClientProvider
     *            the HTTP client provider to be used to create HTTP clients.
     */
    protected RestApiClient(RestApiClientConfig restApiClientConfig, HttpClientProvider httpClientProvider) {
        isNotNull(HTTP_CLIENT_PROVIDER_DISPLAY_NAME, httpClientProvider);
        isNotNull(CONFIG_DISPLAY_NAME, restApiClientConfig);

        this.httpClient = httpClientProvider.createHttpClient(restApiClientConfig.getRoutePlanner());
        this.host = getHostAsURL(restApiClientConfig.getHost());
    }

    private URL getHostAsURL(String host) {
        try {
            return new URL(host);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException(format(HOST_NOT_VALID_MSG, host), e);
        }
    }

    /**
     * An abstract method that should be overridden. Implementation should
     * return the API path of the client as String.
     * 
     * @return API path
     */
    protected abstract String getApiPath();

    /**
     * Construct a URI appending a provided path to the host and API path.
     * 
     * @param pathPattern
     *            pattern used to build the path in the format of
     *            {@link MessageFormat}
     * @param args
     *            arguments used the build the path
     * @return the constructed URI object
     */
    protected URI buildRequestUri(String pathPattern, Object... args) {
        String requestPath = format(pathPattern, args);

        try {
            URI uri = host.toURI();
            return new URIBuilder(uri)
                    .setPath(uri.getPath() + getApiPath() + requestPath)
                    .build();
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(PATH_NOT_VALID_MSG, e);
        }
    }

    /**
     * Construct a URI from the host and API path.
     * 
     * @return the constructed URI object
     */
    protected URI buildRequestUri() {
        return buildRequestUri("");
    }

    /**
     * Executes a {@link Request} and returns a {@link Response} using the
     * default response and status code handlers.
     * 
     * @param 
     *            the type of the request body
     * @param request
     *            the request to be executed
     * @return a Response object constructed from the HTTP response.
     * @throws ConnectionException
     *             thrown in case of an {@link IOException}.
     * @throws ResponseException
     *             may be thrown by the status code handler or in case of an
     *             {@link IOException} while handling response.
     */
    protected  Response execute(Request request)
            throws ConnectionException, ResponseException {
        return execute(request, getDefaultResponseHandler());
    }

    /**
     * Executes a {@link Request} and returns a {@link Response} using the
     * default status code handler and custom {@link ResponseHandler}.
     * 
     * @param 
     *            the type of the request body
     * @param 
     *            the type of the response body
     * @param request
     *            the request to be executed
     * @param responseHandler
     *            the custom response handler.
     * @return a Response object constructed from the HTTP response.
     * @throws ConnectionException
     *             thrown in case of an {@link IOException}.
     * @throws ResponseException
     *             may be thrown by the status code handler or in case of an
     *             {@link IOException} while handling response.
     */
    protected  Response execute(Request request,
            ResponseHandler responseHandler) throws ConnectionException, ResponseException {
        return execute(request, responseHandler, getDefaultStatusCodeHandler());
    }

    /**
     * Executes a {@link Request} and returns a {@link Response} using the
     * default response handler and custom {@link StatusCodeHandler}.
     * 
     * @param 
     *            the type of the request body
     * @param request
     *            the request to be executed
     * @param statusCodeHandler
     *            the custom response handler.
     * @return a Response object constructed from the HTTP response.
     * @throws ConnectionException
     *             thrown in case of an {@link IOException}.
     * @throws ResponseException
     *             may be thrown by the status code handler or in case of an
     *             {@link IOException} while handling response.
     */
    protected  Response execute(Request request, StatusCodeHandler statusCodeHandler)
            throws ConnectionException, ResponseException {
        return execute(request, getDefaultResponseHandler(), statusCodeHandler);
    }

    /**
     * Executes a {@link Request} using custom {@link ResponseHandler} and
     * {@link StatusCodeHandler}.
     * 
     * @param 
     *            the type of the request body
     * @param 
     *            the type of the response body
     * @param request
     *            the request to be executed
     * @param responseHandler
     *            the custom response handler.
     * @param statusCodeHandler
     *            the custom response handler.
     * @return a {@link Response} object constructed from the HTTP response.
     * @throws ConnectionException
     *             thrown in case of an {@link IOException}.
     * @throws ResponseException
     *             may be thrown by the status code handler or in case of an
     *             {@link IOException} while handling response.
     */
    protected  Response execute(Request request,
            ResponseHandler responseHandler, StatusCodeHandler statusCodeHandler)
            throws ConnectionException, ResponseException {
        try {
            HttpResponse httpResponse = httpClient.execute(request.getHttpRequest());
            return handleResponse(request, httpResponse, responseHandler, statusCodeHandler);
        } catch (IOException e) {
            throw new ConnectionException(format(IO_EXCEPTION_WHILE_EXECUTING_REQUEST_MSG, request), e,
                    getStringRequest(request));
        }
    }

    private  Response handleResponse(Request request,
            HttpResponse httpResponse, ResponseHandler responseHandler,
            StatusCodeHandler statusCodeHandler) {
        ResponseType responseEntity = null;
        try {
            responseEntity = responseHandler.handleResponse(httpResponse);
        } catch (IOException e) {
            HttpExchangeContext context = buildContext(request, httpResponse, responseEntity);
            throw new ResponseException(format(IO_EXCEPTION_WHILE_HANDLING_RESPONSE_MSG, context), context, e);
        }
        handleStatusCode(request, httpResponse, responseEntity, statusCodeHandler);
        return new Response(httpResponse, responseEntity);
    }

    private  void handleStatusCode(Request request, HttpResponse httpResponse,
            ResponseType responseEntity, StatusCodeHandler statusCodeHandler) {
        HttpExchangeContext context = buildContext(request, httpResponse, responseEntity);

        statusCodeHandler.handleStatusCode(context.getResponse().getStatusCode(), context);
    }

    private  HttpExchangeContext buildContext(Request request,
            HttpResponse httpResponse, ResponseType responseEntity) {
        Request contextRequest = getStringRequest(request);
        Response contextResponse = getStringResponse(httpResponse, responseEntity);
        HttpExchangeContext context = new HttpExchangeContext(contextRequest, contextResponse);
        return context;
    }

    private  Request getStringRequest(Request request) {
        HttpUriRequest httpRequest = request.getHttpRequest();
        RequestType requestEntity = request.getEntity();

        if (requestEntity == null) {
            return new Request<>(httpRequest);
        }
        if (requestEntity instanceof String) {
            return new Request<>(httpRequest, (String) requestEntity);
        }
        try {
            String requestBody = new ObjectMapper().writeValueAsString(requestEntity);
            return new Request<>(httpRequest, requestBody);
        } catch (JsonProcessingException e) {
            return new Request<>(httpRequest);
        }
    }

    private  Response getStringResponse(HttpResponse httpResponse, ResponseType responseEntity) {
        return new Response<>(httpResponse, responseEntity == null ? "" : responseEntity.toString());
    }

    /**
     * @return Returns a {@link DefaultResponseHandler}
     */
    protected ResponseHandler getDefaultResponseHandler() {
        return new DefaultResponseHandler();
    }

    /**
     * @return Returns a {@link DefaultStatusCodeHandler}
     */
    protected StatusCodeHandler getDefaultStatusCodeHandler() {
        return DefaultStatusCodeHandler.create();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy