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

org.eclipse.ditto.base.model.headers.AbstractDittoHeadersBuilder Maven / Gradle / Ivy

/*
 * Copyright (c) 2017 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.ditto.base.model.headers;

import static org.eclipse.ditto.base.model.common.ConditionChecker.argumentNotEmpty;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotEmpty;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

import org.eclipse.ditto.base.model.acks.AcknowledgementRequest;
import org.eclipse.ditto.base.model.auth.AuthorizationContext;
import org.eclipse.ditto.base.model.auth.AuthorizationSubject;
import org.eclipse.ditto.base.model.common.DittoDuration;
import org.eclipse.ditto.base.model.common.ResponseType;
import org.eclipse.ditto.base.model.exceptions.DittoHeaderInvalidException;
import org.eclipse.ditto.base.model.headers.contenttype.ContentType;
import org.eclipse.ditto.base.model.headers.entitytag.EntityTag;
import org.eclipse.ditto.base.model.headers.entitytag.EntityTagMatchers;
import org.eclipse.ditto.base.model.headers.metadata.MetadataHeader;
import org.eclipse.ditto.base.model.headers.metadata.MetadataHeaderKey;
import org.eclipse.ditto.base.model.headers.metadata.MetadataHeaders;
import org.eclipse.ditto.base.model.json.JsonSchemaVersion;
import org.eclipse.ditto.json.JsonArray;
import org.eclipse.ditto.json.JsonCollectors;
import org.eclipse.ditto.json.JsonFactory;
import org.eclipse.ditto.json.JsonField;
import org.eclipse.ditto.json.JsonFieldSelector;
import org.eclipse.ditto.json.JsonParseOptions;
import org.eclipse.ditto.json.JsonValue;
import org.eclipse.ditto.json.JsonValueContainer;

/**
 * An abstract base implementation for subclasses of {@link org.eclipse.ditto.base.model.headers.DittoHeadersBuilder}.
 * This implementation does already most of the work including header value validation. Insertion order and
 * re-insertion order is maintained via a linked hash map. Since Java linked hash map does not maintain
 * re-insertion order, each entry is removed from the map before they are added.
 */
@NotThreadSafe
public abstract class AbstractDittoHeadersBuilder, R extends DittoHeaders>
        implements DittoHeadersBuilder {

    private static final JsonParseOptions JSON_FIELD_SELECTOR_PARSE_OPTIONS = JsonFactory.newParseOptionsBuilder()
            .withoutUrlDecoding()
            .build();

    private static final Map BUILT_IN_DEFINITIONS;

    static {
        final DittoHeaderDefinition[] dittoHeaderDefinitions = DittoHeaderDefinition.values();
        final Map definitions = new LinkedHashMap<>(dittoHeaderDefinitions.length);
        for (final DittoHeaderDefinition dittoHeaderDefinition : dittoHeaderDefinitions) {
            definitions.put(dittoHeaderDefinition.getKey(), dittoHeaderDefinition);
        }
        BUILT_IN_DEFINITIONS = Collections.unmodifiableMap(definitions);
    }

    protected final S myself;
    private final Map headers;
    private final Map definitions;
    private MetadataHeaders metadataHeaders;
    private JsonFieldSelector getMetadataFieldSelector;
    private JsonFieldSelector deleteMetadataFieldSelector;

    /**
     * Constructs a new {@code AbstractDittoHeadersBuilder} object.
     *
     * @param initialHeaders initial key-value-pairs or an empty map.
     * @param definitions a collection of all well known {@link org.eclipse.ditto.base.model.headers.HeaderDefinition}s
     * of this builder. The definitions are used for header value validation.
     * @param selfType this type is used to simulate the "self type" of the returned object for Method Chaining of
     * the builder methods.
     * @throws NullPointerException if any argument is {@code null}.
     */
    @SuppressWarnings("unchecked")
    protected AbstractDittoHeadersBuilder(final Map initialHeaders,
            final Collection definitions, final Class selfType) {

        checkNotNull(initialHeaders, "initial headers");
        checkNotNull(definitions, "header definitions");
        validateValueTypes(initialHeaders, definitions); // this constructor does validate the known value types
        myself = (S) selfType.cast(this);
        headers = preserveCaseSensitivity(initialHeaders);
        metadataHeaders = MetadataHeaders.newInstance();
        metadataHeaders.addAll(extractMetadataHeaders(headers));
        this.definitions = getHeaderDefinitionsAsMap(definitions);
        getMetadataFieldSelector = extractMetadataFieldSelector(headers, DittoHeaderDefinition.GET_METADATA);
        deleteMetadataFieldSelector = extractMetadataFieldSelector(headers, DittoHeaderDefinition.DELETE_METADATA);
    }

    private static MetadataHeaders extractMetadataHeaders(final Map headers) {
        final MetadataHeaders result;
        final CharSequence putMetadataHeaderCharSequence = headers.remove(DittoHeaderDefinition.PUT_METADATA.getKey());
        if (null != putMetadataHeaderCharSequence) {
            result = MetadataHeaders.parseMetadataHeaders(putMetadataHeaderCharSequence);
        } else {
            result = MetadataHeaders.newInstance();
        }
        return result;
    }

    private static JsonFieldSelector extractMetadataFieldSelector(final Map headers,
            final DittoHeaderDefinition headerDefinition) {
        final JsonFieldSelector result;
        final CharSequence metadataFieldSelector = headers.remove(headerDefinition.getKey());

        if (null != metadataFieldSelector) {
            result = JsonFactory.newFieldSelector(metadataFieldSelector.toString(),
                    JSON_FIELD_SELECTOR_PARSE_OPTIONS);
        } else {
            result = JsonFactory.emptyFieldSelector();
        }
        return result;
    }

    private static Map getHeaderDefinitionsAsMap(
            final Collection headerDefinitions) {

        final DittoHeaderDefinition[] dittoHeaderDefinitions = DittoHeaderDefinition.values();
        final Map result =
                new LinkedHashMap<>(headerDefinitions.size() + dittoHeaderDefinitions.length);
        for (final HeaderDefinition definition : headerDefinitions) {
            result.put(definition.getKey(), definition);
        }
        result.putAll(BUILT_IN_DEFINITIONS);
        return result;
    }

    /**
     * Constructs a new {@code AbstractDittoHeadersBuilder} object based on an existing {@code DittoHeaders} instance
     * applying a performance optimization: skipping the validation of values types as we can be sure that they already
     * are valid when being passed in as DittoHeaders.
     *
     * @param initialHeaders initial DittoHeaders.
     * @param definitions a collection of all well known {@link org.eclipse.ditto.base.model.headers.HeaderDefinition}s
     * of this builder. The definitions are used for header value validation.
     * @param selfType this type is used to simulate the "self type" of the returned object for Method Chaining of
     * the builder methods.
     * @throws NullPointerException if any argument is {@code null}.
     */
    @SuppressWarnings("unchecked")
    protected AbstractDittoHeadersBuilder(final R initialHeaders,
            final Collection definitions, final Class selfType) {

        checkNotNull(initialHeaders, "initialHeaders");
        checkNotNull(definitions, "definitions");
        myself = (S) selfType.cast(this);
        headers = preserveCaseSensitivity(initialHeaders);
        metadataHeaders = MetadataHeaders.newInstance();
        metadataHeaders.addAll(extractMetadataHeaders(headers));
        this.definitions = getHeaderDefinitionsAsMap(definitions);
        getMetadataFieldSelector = extractMetadataFieldSelector(headers, DittoHeaderDefinition.GET_METADATA);
        deleteMetadataFieldSelector = extractMetadataFieldSelector(headers, DittoHeaderDefinition.DELETE_METADATA);
    }

    /**
     * Validates the values of the specified headers with the help of the specified definitions.
     *
     * @param headers the key-value-pairs to be validated.
     * @param definitions perform the actual validation.
     */
    protected void validateValueTypes(final Map headers,
            final Collection definitions) {

        for (final HeaderDefinition definition : definitions) {
            final String value = headers.get(definition.getKey());
            if (null != value) {
                definition.validateValue(value);
            }
        }
    }

    protected static Map toMap(final JsonValueContainer jsonObject) {
        checkNotNull(jsonObject, "JSON object");
        final Map result = new LinkedHashMap<>(jsonObject.getSize());
        jsonObject.forEach(jsonField -> {
            final JsonValue jsonValue = jsonField.getValue();
            if (!jsonValue.isNull()) {
                final String stringValue = jsonValue.isString() ? jsonValue.asString() : jsonValue.toString();
                result.put(jsonField.getKeyName(), stringValue);
            }
        });

        return result;
    }

    @Override
    public S correlationId(@Nullable final CharSequence correlationId) {
        // special handling: as pass-through header, preserve capitalization of original header.
        final String key = DittoHeaderDefinition.CORRELATION_ID.getKey();
        if (correlationId != null) {
            checkNotEmpty(correlationId, "correlationId");
            final Header previousCorrelationId = headers.remove(key);
            if (previousCorrelationId != null) {
                headers.put(key, Header.of(previousCorrelationId.getKey(), correlationId.toString()));
            } else {
                headers.put(key, Header.of(key, correlationId.toString()));
            }
        } else {
            headers.remove(key);
        }
        return myself;
    }

    /**
     * Puts the specified CharSequence value to this builder using the key of the specified definition. If the value
     * is {@code null} a possibly existing value for the same key is removed; thus putting a {@code null} value is same
     * as removing the key-value-pair.
     *
     * @param definition provides the key to be associated with {@code value}.
     * @param value the value to be associated with the key of {@code definition}.
     */
    protected void putCharSequence(final HeaderDefinition definition, @Nullable final CharSequence value) {
        if (null != value) {
            checkNotEmpty(value, definition.getKey());
            headers.remove(definition.getKey());
            headers.put(definition.getKey(), Header.of(definition.getKey(), value.toString()));
        } else {
            removeHeader(definition.getKey());
        }
    }

    @Override
    public S schemaVersion(@Nullable final JsonSchemaVersion schemaVersion) {
        // no-op
        return myself;
    }

    @Override
    public S authorizationContext(@Nullable final AuthorizationContext authorizationContext) {
        if (null != authorizationContext) {
            putJsonValue(DittoHeaderDefinition.AUTHORIZATION_CONTEXT, authorizationContext.toJson());
        } else {
            removeHeader(DittoHeaderDefinition.AUTHORIZATION_CONTEXT.getKey());
        }
        return myself;
    }

    @Override
    public S replyTarget(@Nullable final Integer replyTarget) {
        if (replyTarget != null) {
            putCharSequence(DittoHeaderDefinition.REPLY_TARGET, String.valueOf(replyTarget));
        } else {
            removeHeader(DittoHeaderDefinition.REPLY_TARGET.getKey());
        }
        return myself;
    }

    @Override
    public S expectedResponseTypes(final ResponseType... responseTypes) {
        checkNotNull(responseTypes, "responseTypes");
        final List expectedResponseTypes = Arrays.stream(responseTypes)
                .map(ResponseType::getName)
                .collect(Collectors.toList());
        putStringCollection(DittoHeaderDefinition.EXPECTED_RESPONSE_TYPES, expectedResponseTypes);
        return myself;
    }

    @Override
    public S expectedResponseTypes(final Collection responseTypes) {
        checkNotNull(responseTypes, "responseTypes");
        if (!responseTypes.isEmpty()) {
            final List expectedResponseTypes = responseTypes.stream()
                    .map(ResponseType::getName)
                    .collect(Collectors.toList());
            putStringCollection(DittoHeaderDefinition.EXPECTED_RESPONSE_TYPES, expectedResponseTypes);
        }
        return myself;
    }

    protected void putStringCollection(final HeaderDefinition definition, final Collection collection) {
        checkNotNull(collection, definition.getKey());
        putJsonValue(definition, toJsonValueArray(collection));
    }

    private static JsonValue toJsonValueArray(final Collection stringCollection) {
        return stringCollection.stream()
                .map(JsonFactory::newValue)
                .collect(JsonCollectors.valuesToArray());
    }

    private void putJsonValue(final HeaderDefinition definition, final JsonValue jsonValue) {
        if (!jsonValue.isNull()) {
            putCharSequence(definition, jsonValue.isString() ? jsonValue.asString() : jsonValue.toString());
        }
    }

    @Override
    public S readGrantedSubjects(final Collection readGrantedSubjects) {
        putAuthorizationSubjectCollection(readGrantedSubjects, DittoHeaderDefinition.READ_SUBJECTS);
        return myself;
    }

    private void putAuthorizationSubjectCollection(final Collection authorizationSubjects,
            final HeaderDefinition definition) {

        checkNotNull(authorizationSubjects, definition.getKey());
        final JsonArray authorizationSubjectIdsJsonArray = authorizationSubjects.stream()
                .map(AuthorizationSubject::getId)
                .map(JsonFactory::newValue)
                .collect(JsonCollectors.valuesToArray());
        putJsonValue(definition, authorizationSubjectIdsJsonArray);
    }

    @Override
    public S readRevokedSubjects(final Collection readRevokedSubjects) {
        putAuthorizationSubjectCollection(readRevokedSubjects, DittoHeaderDefinition.READ_REVOKED_SUBJECTS);
        return myself;
    }

    @Override
    public S channel(@Nullable final CharSequence channel) {
        final DittoHeaderDefinition headerDefinition = DittoHeaderDefinition.CHANNEL;
        if (null != channel) {
            final ValueValidator dittoChannelValidator = HeaderValueValidators.getDittoChannelValidator();
            dittoChannelValidator.accept(headerDefinition, channel);
            putCharSequence(headerDefinition, channel.toString().trim().toLowerCase(Locale.ENGLISH));
        } else {
            removeHeader(headerDefinition.getKey());
        }
        return myself;
    }

    @Override
    public S responseRequired(final boolean responseRequired) {
        putBoolean(DittoHeaderDefinition.RESPONSE_REQUIRED, responseRequired);
        return myself;
    }

    protected void putBoolean(final HeaderDefinition definition, final boolean value) {
        putJsonValue(definition, JsonFactory.newValue(value));
    }

    @Override
    public S dryRun(final boolean dryRun) {
        putBoolean(DittoHeaderDefinition.DRY_RUN, dryRun);
        return myself;
    }

    @Override
    public S origin(final CharSequence origin) {
        putCharSequence(DittoHeaderDefinition.ORIGIN, origin);
        return myself;
    }

    @Override
    public S contentType(@Nullable final CharSequence contentType) {
        putCharSequence(DittoHeaderDefinition.CONTENT_TYPE, contentType);
        return myself;
    }

    @Override
    public S contentType(@Nullable final ContentType contentType) {
        if (null != contentType) {
            putCharSequence(DittoHeaderDefinition.CONTENT_TYPE, contentType.getValue());
        } else {
            removeHeader(DittoHeaderDefinition.CONTENT_TYPE.getKey());
        }
        return myself;
    }

    @Override
    public S accept(@Nullable final CharSequence accept) {
        putCharSequence(DittoHeaderDefinition.ACCEPT, accept);
        return myself;
    }

    @Override
    public S eTag(final EntityTag eTag) {
        putCharSequence(DittoHeaderDefinition.ETAG, eTag.toString());
        return myself;
    }

    @Override
    public S ifMatch(final EntityTagMatchers entityTags) {
        putCharSequence(DittoHeaderDefinition.IF_MATCH, entityTags.toString());
        return myself;
    }

    @Override
    public S ifNoneMatch(final EntityTagMatchers entityTags) {
        putCharSequence(DittoHeaderDefinition.IF_NONE_MATCH, entityTags.toString());
        return myself;
    }

    @Override
    public S ifEqual(final IfEqual ifEqual) {
        putCharSequence(DittoHeaderDefinition.IF_EQUAL, ifEqual.toString());
        return myself;
    }

    @Override
    public S inboundPayloadMapper(@Nullable final String inboundPayloadMapperId) {
        putCharSequence(DittoHeaderDefinition.INBOUND_PAYLOAD_MAPPER, inboundPayloadMapperId);
        return myself;
    }

    @Override
    public S acknowledgementRequests(final Collection acknowledgementRequests) {
        checkNotNull(acknowledgementRequests, "acknowledgementRequests");
        putJsonValue(DittoHeaderDefinition.REQUESTED_ACKS, acknowledgementRequests.stream()
                .map(AcknowledgementRequest::toString)
                .map(JsonValue::of)
                .collect(JsonCollectors.valuesToArray()));
        return myself;
    }

    @Override
    public S acknowledgementRequest(final AcknowledgementRequest acknowledgementRequest,
            final AcknowledgementRequest... furtherAcknowledgementRequests) {

        checkNotNull(acknowledgementRequest, "acknowledgementRequest");
        checkNotNull(furtherAcknowledgementRequests, "furtherAcknowledgementRequests");
        final Collection ackRequests =
                new ArrayList<>(1 + furtherAcknowledgementRequests.length);

        ackRequests.add(acknowledgementRequest);
        Collections.addAll(ackRequests, furtherAcknowledgementRequests);
        return acknowledgementRequests(ackRequests);
    }

    @Override
    public S timeout(@Nullable final CharSequence timeoutStr) {
        if (null != timeoutStr) {
            return timeout(tryToParseDuration(timeoutStr));
        }
        return timeout((DittoDuration) null);
    }

    private static DittoDuration tryToParseDuration(final CharSequence duration) {
        try {
            return DittoDuration.parseDuration(duration);
        } catch (final IllegalArgumentException e) {
            throw DittoHeaderInvalidException.newInvalidTypeBuilder(DittoHeaderDefinition.TIMEOUT.getKey(), duration,
                    "duration").build();
        }
    }

    @Override
    public S timeout(@Nullable final Duration timeout) {
        if (null != timeout) {
            return timeout(DittoDuration.of(timeout));
        }
        return timeout((DittoDuration) null);
    }

    private S timeout(@Nullable final DittoDuration timeout) {
        final DittoHeaderDefinition definition = DittoHeaderDefinition.TIMEOUT;
        if (null != timeout) {
            putCharSequence(definition, timeout.toString());
        } else {
            removeHeader(definition.getKey());
        }
        return myself;
    }

    @Override
    public S putMetadata(final MetadataHeaderKey key, final JsonValue value) {
        metadataHeaders.add(MetadataHeader.of(key, value));
        return myself;
    }

    @Override
    public S allowPolicyLockout(final boolean allowPolicyLockout) {
        putBoolean(DittoHeaderDefinition.ALLOW_POLICY_LOCKOUT, allowPolicyLockout);
        return myself;
    }

    @Override
    public S journalTags(final Collection journalTags) {
        putJsonValue(DittoHeaderDefinition.EVENT_JOURNAL_TAGS, journalTags.stream()
                .map(JsonValue::of)
                .collect(JsonCollectors.valuesToArray()));
        return myself;
    }

    @Override
    public S condition(final String condition) {
        putCharSequence(DittoHeaderDefinition.CONDITION, condition);
        return myself;
    }

    @Override
    public S liveChannelCondition(@Nullable final String liveChannelCondition) {
        putCharSequence(DittoHeaderDefinition.LIVE_CHANNEL_CONDITION, liveChannelCondition);
        return myself;
    }

    @Override
    public S putHeader(final CharSequence key, final CharSequence value) {
        validateKey(key);
        checkNotNull(value, "value");
        final String keyString = key.toString().toLowerCase();
        validateValueType(keyString, value);
        if (isPutMetadataKey(keyString)) {
            metadataHeaders = MetadataHeaders.parseMetadataHeaders(value);
        } else if (isGetMetadataKey(keyString)) {
            getMetadataFieldSelector =
                    JsonFactory.newFieldSelector(value.toString(), JSON_FIELD_SELECTOR_PARSE_OPTIONS);
        } else if (isDeleteMetadataKey(keyString)) {
            deleteMetadataFieldSelector =
                    JsonFactory.newFieldSelector(value.toString(), JSON_FIELD_SELECTOR_PARSE_OPTIONS);
        } else if (DittoHeaderDefinition.CORRELATION_ID.getKey().equals(keyString)) {
            correlationId(value);
        } else {
            headers.remove(keyString);
            headers.put(keyString, Header.of(key.toString(), value.toString()));
        }
        return myself;
    }

    private static void validateKey(final CharSequence key) {
        argumentNotEmpty(key, "key");
    }

    protected void validateValueType(final CharSequence key, final CharSequence value) {
        @Nullable final HeaderDefinition headerDefinition = definitions.get(key.toString());
        if (null != headerDefinition) {
            headerDefinition.validateValue(value);
        }
    }

    private static boolean isPutMetadataKey(final CharSequence key) {
        return Objects.equals(DittoHeaderDefinition.PUT_METADATA.getKey(), key.toString());
    }

    private static boolean isGetMetadataKey(final CharSequence key) {
        return Objects.equals(DittoHeaderDefinition.GET_METADATA.getKey(), key.toString());
    }

    private static boolean isDeleteMetadataKey(final CharSequence key) {
        return Objects.equals(DittoHeaderDefinition.DELETE_METADATA.getKey(), key.toString());
    }

    @Override
    public S putHeaders(final Map headers) {
        checkNotNull(headers, "headers");
        headers.forEach(this::putHeader);
        return myself;
    }

    @Override
    public S removeHeader(final CharSequence key) {
        validateKey(key);
        final String keyString = key.toString().toLowerCase();
        headers.remove(keyString);
        if (isPutMetadataKey(keyString)) {
            metadataHeaders.clear();
        }
        if (isGetMetadataKey(keyString)) {
            getMetadataFieldSelector = JsonFactory.emptyFieldSelector();
        }
        if (isDeleteMetadataKey(keyString)) {
            deleteMetadataFieldSelector = JsonFactory.emptyFieldSelector();
        }

        return myself;
    }

    @Override
    public S removePreconditionHeaders() {
        headers.remove(DittoHeaderDefinition.IF_MATCH.getKey());
        headers.remove(DittoHeaderDefinition.IF_NONE_MATCH.getKey());
        return myself;
    }

    @Override
    public S traceparent(@Nullable final CharSequence traceparent) {
        if (traceparent != null) {
            putCharSequence(DittoHeaderDefinition.W3C_TRACEPARENT, traceparent);
        }
        return myself;
    }

    @Override
    public S tracestate(@Nullable final CharSequence tracestate) {
        if (tracestate != null && tracestate.length() > 0) {
            putCharSequence(DittoHeaderDefinition.W3C_TRACESTATE, tracestate);
        }
        return myself;
    }

    @Override
    public R build() {
        putMetadataHeadersToRegularHeaders();
        putGetMetadataFieldSelectorToRegularHeaders();
        putDeleteMetadataFieldSelectorToRegularHeaders();

        final ImmutableDittoHeaders dittoHeaders = ImmutableDittoHeaders.fromBuilder(headers);
        return doBuild(dittoHeaders);
    }

    private void putMetadataHeadersToRegularHeaders() {
        if (!metadataHeaders.isEmpty()) {
            headers.put(DittoHeaderDefinition.PUT_METADATA.getKey(),
                    Header.of(DittoHeaderDefinition.PUT_METADATA.getKey(), metadataHeaders.toJsonString()));
        }
    }

    private void putGetMetadataFieldSelectorToRegularHeaders() {
        if (!getMetadataFieldSelector.isEmpty()) {
            headers.put(DittoHeaderDefinition.GET_METADATA.getKey(),
                    Header.of(DittoHeaderDefinition.GET_METADATA.getKey(), getMetadataFieldSelector.toString()));
        }
    }

    private void putDeleteMetadataFieldSelectorToRegularHeaders() {
        if (!deleteMetadataFieldSelector.isEmpty()) {
            headers.put(DittoHeaderDefinition.DELETE_METADATA.getKey(),
                    Header.of(DittoHeaderDefinition.DELETE_METADATA.getKey(), deleteMetadataFieldSelector.toString()));
        }
    }

    @Override
    public String toString() {
        putMetadataHeadersToRegularHeaders();
        putGetMetadataFieldSelectorToRegularHeaders();
        putDeleteMetadataFieldSelectorToRegularHeaders();
        return headers.toString();
    }

    protected abstract R doBuild(DittoHeaders dittoHeaders);

    private static Map preserveCaseSensitivity(final Map headers) {
        if (headers instanceof AbstractDittoHeaders) {
            return new LinkedHashMap<>(((AbstractDittoHeaders) headers).headers);
        } else {
            final LinkedHashMap result = new LinkedHashMap<>();
            headers.forEach((k, v) -> result.put(k.toLowerCase(), Header.of(k, v)));
            return result;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy