ChainedHttpConfig.java

/**
 * Copyright (C) 2017 HttpBuilder-NG Project
 *
 * 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 groovyx.net.http;

import java.net.HttpCookie;
import java.nio.charset.Charset;
import java.util.AbstractMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;

import static groovyx.net.http.Traverser.traverse;

public interface ChainedHttpConfig extends HttpConfig {

    interface ChainedRequest extends Request {
        ChainedRequest getParent();

        List<HttpCookie> getCookies();

        Object getBody();

        String getContentType();

        Map<String, BiConsumer<ChainedHttpConfig, ToServer>> getEncoderMap();

        Charset getCharset();

        default Charset actualCharset() {
            return traverse(this, ChainedRequest::getParent, ChainedRequest::getCharset, Traverser::notNull);
        }

        default String actualContentType() {
            return traverse(this, ChainedRequest::getParent, ChainedRequest::getContentType, Traverser::notNull);
        }

        default Object actualBody() {
            return traverse(this, ChainedRequest::getParent, ChainedRequest::getBody, Traverser::notNull);
        }

        default Map<String, CharSequence> actualHeaders(final Map<String, CharSequence> map) {
            Predicate<Map<String, CharSequence>> addValues = (headers) -> {
                map.putAll(headers);
                return false;
            };
            traverse(this, ChainedRequest::getParent, Request::getHeaders, addValues);
            return map;
        }

        default BiConsumer<ChainedHttpConfig, ToServer> actualEncoder(final String contentType) {
            final Function<ChainedRequest, BiConsumer<ChainedHttpConfig, ToServer>> theValue = (cr) -> {
                BiConsumer<ChainedHttpConfig, ToServer> ret = cr.encoder(contentType);
                if (ret != null) {
                    return ret;
                } else {
                    return cr.encoder(ContentTypes.ANY.getAt(0));
                }
            };

            return traverse(this, ChainedRequest::getParent, theValue, Traverser::notNull);
        }

        default Auth actualAuth() {
            final Predicate<Auth> choose = (a) -> a != null && a.getAuthType() != null;
            return traverse(this, ChainedRequest::getParent, Request::getAuth, choose);
        }

        default List<HttpCookie> actualCookies(final List<HttpCookie> list) {
            Predicate<List<HttpCookie>> addAll = (cookies) -> {
                list.addAll(cookies);
                return false;
            };
            traverse(this, ChainedRequest::getParent, ChainedRequest::getCookies, addAll);
            return list;
        }

        HttpVerb getVerb();

        void setVerb(HttpVerb verb);
    }

    interface ChainedResponse extends Response {
        ChainedResponse getParent();

        Class<?> getType();

        Function<Throwable,?> getException();

        default BiFunction<FromServer, Object, ?> actualAction(final Integer code) {
            return traverse(this, ChainedResponse::getParent, (cr) -> cr.when(code), Traverser::notNull);
        }

        default Function<Throwable,?> actualException() {
            return traverse(this, ChainedResponse::getParent, ChainedResponse::getException, Traverser::notNull);
        }

        default BiFunction<ChainedHttpConfig, FromServer, Object> actualParser(final String contentType) {
            final Function<ChainedResponse, BiFunction<ChainedHttpConfig, FromServer, Object>> theValue = (cr) -> {
                BiFunction<ChainedHttpConfig, FromServer, Object> ret = cr.parser(contentType);
                if (ret != null) {
                    return ret;
                } else {
                    return cr.parser(ContentTypes.ANY.getAt(0));
                }
            };

            return traverse(this, ChainedResponse::getParent, theValue, Traverser::notNull);
        }
    }

    Map<Map.Entry<String, Object>, Object> getContextMap();

    default Object actualContext(final String contentType, final Object id) {
        final Map.Entry<String, Object> key = new AbstractMap.SimpleImmutableEntry<>(contentType, id);
        final Map.Entry<String, Object> anyKey = new AbstractMap.SimpleImmutableEntry<>(ContentTypes.ANY.getAt(0), id);

        final Function<ChainedHttpConfig, Object> theValue = (config) -> {
            Object ctx = config.getContextMap().get(key);
            if (ctx != null) {
                return ctx;
            } else {
                return config.getContextMap().get(anyKey);
            }
        };

        return traverse(this, ChainedHttpConfig::getParent, theValue, Traverser::notNull);
    }

    ChainedResponse getChainedResponse();

    ChainedRequest getChainedRequest();

    ChainedHttpConfig getParent();

    default BiFunction<ChainedHttpConfig, FromServer, Object> findParser(final String contentType) {
        final BiFunction<ChainedHttpConfig, FromServer, Object> found = getChainedResponse().actualParser(contentType);
        return found == null ? NativeHandlers.Parsers::streamToBytes : found;
    }

    /**
     * Used to find the encoder configured to encode the current resolved content-type.
     *
     * @return the configured encoder
     * @throws IllegalStateException if no coder was found
     */
    default BiConsumer<ChainedHttpConfig, ToServer> findEncoder() {
        return findEncoder(findContentType());
    }

    /**
     * Used to find the encoder configured to encode the specified content-type.
     *
     * @param contentType the content-type to be encoded
     * @return the configured encoder
     * @throws IllegalStateException if no coder was found
     */
    default BiConsumer<ChainedHttpConfig, ToServer> findEncoder(final String contentType) {
        final BiConsumer<ChainedHttpConfig, ToServer> encoder = getChainedRequest().actualEncoder(contentType);
        if (encoder == null) {
            throw new IllegalStateException("Could not find encoder for content-type (" + contentType + ")");
        }

        return encoder;
    }

    default String findContentType() {
        final String contentType = getChainedRequest().actualContentType();
        if (contentType == null) {
            throw new IllegalStateException("Found request body, but content type is undefined");
        }

        return contentType;
    }

    default Charset findCharset(){
        return getChainedRequest().actualCharset();
    }
}