io.helixservice.feature.restclient.RestRequest.java Source code

Java tutorial

Introduction

Here is the source code for io.helixservice.feature.restclient.RestRequest.java

Source

/*
 * @author Les Novell
 *
 *   All rights reserved. This program and the accompanying materials
 *   are made available under the terms of the Eclipse Public License v1.0
 *   and Apache License v2.0 which accompanies this distribution.
 *
 *      The Apache License v2.0 is available at
 *      http://www.opensource.org/licenses/apache2.0.php
 *
 */

package io.helixservice.feature.restclient;

import co.paralleluniverse.fibers.SuspendExecution;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.helixservice.core.util.VertxTypeConverter;
import io.helixservice.feature.restservice.marshal.Marshaller;
import io.helixservice.feature.restservice.marshal.Message;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;

import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

import static io.vertx.ext.sync.Sync.awaitEvent;

/**
 * Fluent interface for a pending REST request
 */
public class RestRequest {
    private final Supplier<Marshaller> marshallerSupplier;
    private final Supplier<HttpClient> httpClient;
    private final HttpMethod method;
    private final String urlPath;
    private final Optional<Object> requestBody;

    private Multimap<String, String> headers = HashMultimap.create();
    private Map<String, String> pathVariables = new HashMap<>();
    private Map<String, String> parameters = new HashMap<>();
    private Long timeoutInMs;
    private boolean useDefaultHostAndPort = false;

    /**
     * Use RestClient factory methods to create the initial RestRequest
     */
    RestRequest(Supplier<Marshaller> marshallerSupplier, Supplier<HttpClient> httpClient, HttpMethod method,
            String urlPath, Optional<Object> requestBody) {
        this.marshallerSupplier = marshallerSupplier;
        this.httpClient = httpClient;
        this.method = method;
        this.urlPath = urlPath;
        this.requestBody = requestBody;
    }

    /**
     * Set a timeout for this request. Overrides the default HTTP timeout defined in VertxHTTPOptions.
     *
     * @param timeoutInMs Timeout in milliseconds
     */
    public void setTimeout(long timeoutInMs) {
        this.timeoutInMs = timeoutInMs;
    }

    /**
     * Clear any headers that might be inserted automatically before the request.
     * Provides full control over headers sent.
     *
     * @return This fluent interface
     */
    public RestRequest clearDefaultHeaders() {
        this.headers.clear();
        return this;
    }

    /**
     * If true, then this request will use the default
     * host and port configured originally in the VertxHTTPOptions.
     * <p>
     * Host and Port can be omitted, as they will be replaced
     * if this parameter is true.
     *
     * @return This fluent interface
     */
    public RestRequest useDefaultHostAndPort() {
        this.useDefaultHostAndPort = true;
        return this;
    }

    /**
     * Add a header to this request
     *
     * @param name Name of the header
     * @param value Value of the header
     * @return This fluent interface
     */
    public RestRequest header(String name, String value) {
        this.headers.put(name, value);
        return this;
    }

    /**
     * Add a multimap of headers to this request
     *
     * @param headers Map of headers to add to this request
     * @return This fluent interface
     */
    public RestRequest headers(Multimap<String, String> headers) {
        this.headers.putAll(headers);
        return this;
    }

    /**
     * Replace REST path variable inside the provided URI.
     * <p>
     * Path variables can be defined in the URI by surrounding
     * the variable name with curly braces. For Example,
     * <pre>
     *     http://localhost:8080/document/{documentId}
     * </pre>
     *
     * @param variable Variable name
     * @param value Variable value
     * @return This fluent interface
     */
    public RestRequest pathVariable(String variable, String value) {
        this.pathVariables.put(variable, value);
        return this;
    }

    /**
     * Replace REST path variables inside the provided URI.
     * <p>
     * Path variables can be defined in the URI by surrounding
     * the variable name with curly braces. For Example,
     * <pre>
     *     http://localhost:8080/document/{documentId}
     * </pre>
     *
     * @param pathVariables Map of path variables to add to this request
     * @return This fluent interface
     */
    public RestRequest pathVariables(Map<String, String> pathVariables) {
        this.pathVariables.putAll(pathVariables);
        return this;
    }

    /**
     * Add URI parameters to the request
     *
     * @param parameters Map of parameters
     * @return This fluent interface
     */
    public RestRequest parameters(Map<String, String> parameters) {
        this.parameters.putAll(parameters);
        return this;
    }

    /**
     * Add URI a parameter to the request
     *
     * @param parameter Name of the parameter
     * @param value Value of the parameter
     * @return This fluent interface
     */
    public RestRequest parameter(String parameter, String value) {
        this.parameters.put(parameter, value);
        return this;
    }

    /**
     * Execute the request, with the expected response body marshaled as a String
     *
     * @return RestResponse fluent interface
     * @throws SuspendExecution For Vert.x Sync
     */
    public RestResponse<String> asString() throws SuspendExecution {
        return asObject(String.class);
    }

    /**
     * Execute the request, with the expected response body marshaled as a byte array
     *
     * @return RestResponse fluent interface
     * @throws SuspendExecution For Vert.x Sync
     */
    public RestResponse<byte[]> asByteArray() throws SuspendExecution {
        return asObject(byte[].class);
    }

    /**
     * Execute the request, with the expected response body marshaled
     * to a specific object type.
     *
     * @param responseType Type we expect the response to be marshaled to
     * @return RestResponse fluent interface
     * @throws SuspendExecution For Vert.x Sync
     */
    public <T> RestResponse<T> asObject(Class<T> responseType) throws SuspendExecution {
        try {
            // Apply Params & Url Vars
            String modifiedUrlPath = addParameters(replaceUrlVars(urlPath));

            // Do request
            HttpClientRequest request;
            if (useDefaultHostAndPort) {
                request = httpClient.get().request(io.vertx.core.http.HttpMethod.valueOf(method.name()),
                        modifiedUrlPath);
            } else {
                request = httpClient.get().requestAbs(io.vertx.core.http.HttpMethod.valueOf(method.name()),
                        modifiedUrlPath);
            }

            // Set timeout, if requested
            if (timeoutInMs != null) {
                request.setTimeout(timeoutInMs);
            }

            // With headers
            request.headers().addAll(VertxTypeConverter.toVertxMultiMap(headers));

            // Write body if we need to
            Buffer body = Buffer.buffer();
            if (requestBody.isPresent()) {
                request.setChunked(true);
                Message message = marshallerSupplier.get().marshal(requestBody);

                List<String> contentTypes = message.getContentTypes();
                if (contentTypes != null && contentTypes.size() > 0) {
                    request.putHeader("Content-Type", contentTypes);
                }

                body = body.appendBytes(message.getBody());
            }

            // Wait for response with Vert.x Sync
            HttpClientResponse httpClientResponse = getHttpClientResponse(request, body);
            Buffer bodyBuffer = getBuffer(httpClientResponse);

            return new RestResponse<>(httpClientResponse, bodyBuffer, marshallerSupplier, responseType);
        } catch (URISyntaxException | UnsupportedEncodingException e) {
            throw new IllegalArgumentException("Unable to parse urlPath=" + urlPath, e);
        }
    }

    private Buffer getBuffer(HttpClientResponse httpClientResponse) throws SuspendExecution {
        return awaitEvent(httpClientResponse::bodyHandler);
    }

    private HttpClientResponse getHttpClientResponse(HttpClientRequest request, Buffer body)
            throws SuspendExecution {
        return awaitEvent(h -> {
            request.handler(h);
            request.write(body);
            request.end();
        });
    }

    private String addParameters(String urlPath) throws URISyntaxException, UnsupportedEncodingException {
        StringBuffer result = new StringBuffer(urlPath);

        if (parameters.size() > 0) {
            boolean first = true;
            result.append("?");
            for (Map.Entry<String, String> paramEntry : parameters.entrySet()) {
                if (!first) {
                    result.append("&");
                }
                result.append(URLEncoder.encode(paramEntry.getKey(), "UTF-8"));
                result.append("=");
                result.append(URLEncoder.encode(paramEntry.getValue(), "UTF-8"));
                first = false;
            }
        }

        return result.toString();
    }

    private String replaceUrlVars(String urlPath) {
        for (Map.Entry<String, String> pathVarEntry : pathVariables.entrySet()) {
            urlPath = urlPath.replace("{" + pathVarEntry.getKey() + "}", pathVarEntry.getValue());
        }
        return urlPath;
    }
}