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

com.palantir.dialogue.Request Maven / Gradle / Ivy

/*
 * (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
 *
 * 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 com.palantir.dialogue;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.palantir.logsafe.Preconditions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;

/** Defines the parameters of a single call to an {@link Endpoint}. */
@ThreadSafe
public final class Request {

    private final ListMultimap headerParams;
    private final ListMultimap queryParams;
    private final ListMultimap pathParams;
    private final Optional body;
    private final RequestAttachments attachments;

    private Request(Builder builder) {
        body = builder.body;
        headerParams = builder.unmodifiableHeaderParams();
        queryParams = builder.unmodifiableQueryParams();
        pathParams = builder.unmodifiablePathParams();
        this.attachments = builder.attachments != null ? builder.attachments : RequestAttachments.create();
    }

    /**
     * The HTTP headers for this request, encoded as a map of {@code header-name: header-value}.
     * Headers names are compared in a case-insensitive fashion as per
     * https://tools.ietf.org/html/rfc7540#section-8.1.2.
     */
    public ListMultimap headerParams() {
        return headerParams;
    }

    /**
     * The URL query parameters headers for this request. These should *not* be URL-encoded and {@link Channel}s are
     * responsible for performing the appropriate encoding if necessary.
     */
    public ListMultimap queryParams() {
        return queryParams;
    }

    /**
     * The HTTP path parameters for this request, encoded as a map of {@code param-name: param-value}. There is a
     * one-to-one correspondence between variable {@link PathTemplate.Segment path segments} of a {@link PathTemplate}
     * and the request's {@link #pathParams}.
     *
     * @deprecated in favor of {@link #pathParameters()} which returns a multimap
     */
    @Deprecated
    public Map pathParams() {
        return MultimapAsMap.of(pathParams);
    }

    /**
     * The HTTP path parameters for this request, encoded as a multimap of {@code param-name: param-value}. There is a
     * one-to-many correspondence between variable {@link PathTemplate.Segment path segments} of a {@link PathTemplate}
     * and the request's {@link #pathParams}.
     */
    public ListMultimap pathParameters() {
        return pathParams;
    }

    /** The HTTP request body for this request or empty if this request does not contain a body. */
    public Optional body() {
        return body;
    }

    /**
     * The mutable request attachments for this request. Attachments will be propagated when this request is mutated
     * through the builder
     */
    public RequestAttachments attachments() {
        return attachments;
    }

    @Override
    public String toString() {
        return "Request{"
                // Values are excluded to avoid the risk of logging credentials
                + "headerParamsKeys="
                + headerParams.keySet()
                + ", queryParams="
                + queryParams
                + ", pathParams="
                + pathParams
                + ", body="
                + body
                + '}';
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (other == null || getClass() != other.getClass()) {
            return false;
        }
        Request request = (Request) other;
        return headerParams.equals(request.headerParams)
                && queryParams.equals(request.queryParams)
                && pathParams.equals(request.pathParams)
                && body.equals(request.body);
    }

    @Override
    public int hashCode() {
        return Objects.hash(headerParams, queryParams, pathParams, body);
    }

    public static Builder builder() {
        return new Builder();
    }

    @NotThreadSafe
    public static final class Builder {

        @SuppressWarnings("UnnecessaryLambda") // Avoid unnecessary allocation
        private static final com.google.common.base.Supplier> MAP_VALUE_FACTORY = () -> new ArrayList<>(1);

        private static final int MUTABLE_HEADERS_MASK = 1;
        private static final int MUTABLE_QUERY_MASK = 1 << 1;
        private static final int MUTABLE_PATH_MASK = 1 << 2;

        private ListMultimap headerParams = ImmutableListMultimap.of();

        private ListMultimap queryParams = ImmutableListMultimap.of();

        private ListMultimap pathParams = ImmutableListMultimap.of();

        private Optional body = Optional.empty();

        @Nullable
        private RequestAttachments attachments;

        private int mutableCollectionsBitSet = 0;

        private Builder() {}

        public Request.Builder from(Request existing) {
            Preconditions.checkNotNull(existing, "Request.build().from() requires a non-null instance");

            headerParams = existing.headerParams;
            queryParams = existing.queryParams;
            pathParams = existing.pathParams;
            attachments = existing.attachments;

            Optional bodyOptional = existing.body();
            if (bodyOptional.isPresent()) {
                body(bodyOptional);
            }
            return this;
        }

        public Request.Builder putHeaderParams(String key, String... values) {
            return putAllHeaderParams(key, Arrays.asList(values));
        }

        public Request.Builder putHeaderParams(String key, String value) {
            Preconditions.checkNotNull(key, "Header name must not be null");
            mutableHeaderParams().put(key, value);
            return this;
        }

        public Request.Builder headerParams(Multimap entries) {
            mutableHeaderParams().clear();
            return putAllHeaderParams(entries);
        }

        public Request.Builder putAllHeaderParams(String key, Iterable values) {
            Preconditions.checkNotNull(key, "Header name must not be null");
            mutableHeaderParams().putAll(key, values);
            return this;
        }

        public Request.Builder putAllHeaderParams(Multimap entries) {
            mutableHeaderParams().putAll(entries);
            return this;
        }

        public Request.Builder putQueryParams(String key, String... values) {
            Preconditions.checkNotNull(key, "Query parameter name must not be null");
            mutableQueryParams().putAll(key, Arrays.asList(values));
            return this;
        }

        public Request.Builder putQueryParams(String key, String value) {
            Preconditions.checkNotNull(key, "Query parameter name must not be null");
            Preconditions.checkNotNull(value, "Query parameter value must not be null");
            mutableQueryParams().put(key, value);
            return this;
        }

        public Request.Builder putQueryParams(Map.Entry entry) {
            Preconditions.checkNotNull(entry.getKey(), "Query parameter name must not be null");
            Preconditions.checkNotNull(entry.getValue(), "Query parameter value must not be null");
            mutableQueryParams().put(entry.getKey(), entry.getValue());
            return this;
        }

        public Request.Builder queryParams(Multimap entries) {
            mutableQueryParams().clear();
            return putAllQueryParams(entries);
        }

        public Request.Builder putAllQueryParams(String key, Iterable values) {
            Preconditions.checkNotNull(key, "Query parameter name must not be null");
            mutableQueryParams().putAll(key, values);
            return this;
        }

        public Request.Builder putAllQueryParams(Multimap entries) {
            mutableQueryParams().putAll(entries);
            return this;
        }

        public Request.Builder putPathParams(String key, String value) {
            Preconditions.checkNotNull(key, "Path parameter name must not be null");
            Preconditions.checkNotNull(value, "Path parameter value must not be null");
            mutablePathParams().put(key, value);
            return this;
        }

        public Request.Builder putPathParams(Map.Entry entry) {
            Preconditions.checkNotNull(entry.getKey(), "Path parameter name must not be null");
            Preconditions.checkNotNull(entry.getValue(), "Path parameter value must not be null");
            mutablePathParams().put(entry.getKey(), entry.getValue());
            return this;
        }

        public Request.Builder pathParams(Map entries) {
            mutablePathParams().clear();
            return putAllPathParams(entries);
        }

        public Request.Builder putAllPathParams(String key, Iterable values) {
            Preconditions.checkArgumentNotNull(key, "Path parameter name must not be null");
            mutablePathParams().putAll(key, values);
            return this;
        }

        public Request.Builder putAllPathParams(Map entries) {
            entries.forEach(mutablePathParams()::put);
            return this;
        }

        public Request.Builder putAllPathParams(Multimap entries) {
            mutablePathParams().putAll(entries);
            return this;
        }

        public Request.Builder body(RequestBody value) {
            body = Optional.of(Preconditions.checkNotNull(value, "body"));
            return this;
        }

        @SuppressWarnings("unchecked")
        public Request.Builder body(Optional value) {
            body = (Optional) value;
            return this;
        }

        private ListMultimap mutableHeaderParams() {
            if (!isHeaderMutable()) {
                setHeaderMutable();
                ListMultimap mutable =
                        Multimaps.newListMultimap(new TreeMap<>(String.CASE_INSENSITIVE_ORDER), MAP_VALUE_FACTORY);
                if (!headerParams.isEmpty()) {
                    // Outperforms mutable.putAll(headerParams)
                    headerParams.forEach(mutable::put);
                }
                headerParams = mutable;
            }
            return headerParams;
        }

        private ListMultimap mutableQueryParams() {
            if (!isQueryMutable()) {
                setQueryMutable();
                queryParams = Multimaps.newListMultimap(new LinkedHashMap<>(), MAP_VALUE_FACTORY);
            }
            return queryParams;
        }

        private ListMultimap mutablePathParams() {
            if (!isPathMutable()) {
                setPathMutable();
                pathParams = ArrayListMultimap.create(pathParams);
            }
            return pathParams;
        }

        private ListMultimap unmodifiableHeaderParams() {
            return isHeaderMutable() ? Multimaps.unmodifiableListMultimap(headerParams) : headerParams;
        }

        private ListMultimap unmodifiableQueryParams() {
            return isQueryMutable() ? Multimaps.unmodifiableListMultimap(queryParams) : queryParams;
        }

        private ListMultimap unmodifiablePathParams() {
            return isPathMutable() ? Multimaps.unmodifiableListMultimap(pathParams) : pathParams;
        }

        public Request build() {
            return new Request(this);
        }

        private boolean isQueryMutable() {
            return getBitFlag(MUTABLE_QUERY_MASK);
        }

        private void setQueryMutable() {
            setBitFlag(MUTABLE_QUERY_MASK);
        }

        private boolean isHeaderMutable() {
            return getBitFlag(MUTABLE_HEADERS_MASK);
        }

        private void setHeaderMutable() {
            setBitFlag(MUTABLE_HEADERS_MASK);
        }

        private boolean isPathMutable() {
            return getBitFlag(MUTABLE_PATH_MASK);
        }

        private void setPathMutable() {
            setBitFlag(MUTABLE_PATH_MASK);
        }

        private boolean getBitFlag(int mask) {
            return (mutableCollectionsBitSet & mask) != 0;
        }

        private void setBitFlag(int flag) {
            mutableCollectionsBitSet |= flag;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy