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

org.citrusframework.http.message.HttpMessageConverter Maven / Gradle / Ivy

/*
 * Copyright the original author or authors.
 *
 * 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 org.citrusframework.http.message;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import jakarta.servlet.http.Cookie;
import org.citrusframework.context.TestContext;
import org.citrusframework.http.client.HttpEndpointConfiguration;
import org.citrusframework.message.Message;
import org.citrusframework.message.MessageConverter;
import org.citrusframework.message.MessageHeaderUtils;
import org.citrusframework.message.MessageHeaders;
import org.citrusframework.util.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMethod;

import static java.util.stream.Collectors.joining;

/**
 * Message converter implementation able to convert HTTP request and response entities to internal message
 * representation and other way round.
 *
 * @since 2.0
 */
public class HttpMessageConverter implements MessageConverter, HttpEntity, HttpEndpointConfiguration> {

    private final CookieConverter cookieConverter;

    public HttpMessageConverter() {
        cookieConverter = new CookieConverter();
    }

    public HttpMessageConverter(CookieConverter cookieConverter) {
        this.cookieConverter = cookieConverter;
    }

    private static String resolveCookieValue(TestContext context, Cookie cookie) {
        return Objects.isNull(context) ? cookie.getValue() : context.replaceDynamicContentInString(cookie.getValue());
    }

    @Override
    public HttpEntity convertOutbound(Message message,
                                         HttpEndpointConfiguration endpointConfiguration,
                                         TestContext context) {

        HttpMessage httpMessage = convertOutboundMessage(message);

        HttpHeaders httpHeaders = createHttpHeaders(httpMessage, endpointConfiguration);

        for (Cookie cookie : httpMessage.getCookies()) {
            httpHeaders.add(
                    HttpHeaders.COOKIE,
                    cookie.getName() + "=" + resolveCookieValue(context, cookie));
        }

        Object payload = httpMessage.getPayload();
        if (httpMessage.getStatusCode() != null) {
            return new ResponseEntity<>(payload, httpHeaders, httpMessage.getStatusCode());
        }

        RequestMethod method = determineRequestMethod(endpointConfiguration, httpMessage);

        return createHttpEntity(httpHeaders, payload, method);
    }

    @Override
    public HttpMessage convertInbound(HttpEntity message,
                                      HttpEndpointConfiguration endpointConfiguration,
                                      TestContext context) {
        Map mappedHeaders = endpointConfiguration.getHeaderMapper().toHeaders(message.getHeaders());
        HttpMessage httpMessage = new HttpMessage(extractMessageBody(message), convertHeaderTypes(mappedHeaders));

        for (Map.Entry customHeader : getCustomHeaders(message.getHeaders(), mappedHeaders).entrySet()) {
            httpMessage.setHeader(customHeader.getKey(), customHeader.getValue());
        }

        if (message instanceof ResponseEntity) {
            httpMessage.status(((ResponseEntity) message).getStatusCode());

            // We've no information here about the HTTP Version in this context.
            // Because HTTP/2 is not supported anyway currently, this should be acceptable.
            httpMessage.version("HTTP/1.1");

            if (endpointConfiguration.isHandleCookies()) {
                httpMessage.setCookies(cookieConverter.convertCookies(message));
            }
        }

        return httpMessage;
    }

    @Override
    public void convertOutbound(HttpEntity externalMessage,
                                Message internalMessage,
                                HttpEndpointConfiguration endpointConfiguration,
                                TestContext context) {
        throw new UnsupportedOperationException("HttpMessageConverter does not support predefined HttpEntity objects");
    }

    /**
     * Message headers consist of standard HTTP message headers and custom headers.
     * This method assumes that all header entries that were not initially mapped
     * by header mapper implementations are custom headers.
     *
     * @param httpHeaders all message headers in their pre nature.
     * @param mappedHeaders the previously mapped header entries (all standard headers).
     * @return The map of custom headers
     */
    private Map getCustomHeaders(HttpHeaders httpHeaders, Map mappedHeaders) {
        Map customHeaders = new HashMap<>();

        for (Map.Entry> header : httpHeaders.entrySet()) {
            if (!mappedHeaders.containsKey(header.getKey())) {
                customHeaders.put(header.getKey(), String.join(",", header.getValue()));
            }
        }

        return customHeaders;
    }

    /**
     * Checks for collection typed header values and convert them to comma delimited String.
     * We need this for further header processing e.g when forwarding headers to JMS queues.
     *
     * @param headers the http request headers.
     */
    private Map convertHeaderTypes(Map headers) {
        Map convertedHeaders = new HashMap<>();

        for (Map.Entry header : headers.entrySet()) {
            if (header.getValue() instanceof Collection value) {
                convertedHeaders.put(header.getKey(), value.stream().map(String::valueOf).collect(joining(",")));
            } else if (header.getValue() instanceof MediaType) {
                convertedHeaders.put(header.getKey(), header.getValue().toString());
            } else {
                convertedHeaders.put(header.getKey(), header.getValue());
            }
        }

        return convertedHeaders;
    }

    /**
     * Creates HttpHeaders based on the outbound message and the endpoint configurations header mapper.
     * @param httpMessage The HttpMessage to copy the headers from
     * @param endpointConfiguration The endpoint configuration to get th header mapper from
     */
    private HttpHeaders createHttpHeaders(HttpMessage httpMessage,
                                          HttpEndpointConfiguration endpointConfiguration) {

        HttpHeaders httpHeaders = new HttpHeaders();

        endpointConfiguration
                .getHeaderMapper()
                .fromHeaders(
                        new org.springframework.messaging.MessageHeaders(httpMessage.getHeaders()),
                        httpHeaders);

        Map messageHeaders = httpMessage.getHeaders();
        for (Map.Entry header : messageHeaders.entrySet()) {
            if (!header.getKey().startsWith(MessageHeaders.PREFIX) &&
                    !MessageHeaderUtils.isSpringInternalHeader(header.getKey()) &&
                    !httpHeaders.containsKey(header.getKey())) {
                httpHeaders.add(header.getKey(), header.getValue().toString());
            }
        }

        if (httpHeaders.getFirst(HttpMessageHeaders.HTTP_CONTENT_TYPE) == null) {
            httpHeaders.add(HttpMessageHeaders.HTTP_CONTENT_TYPE, composeContentTypeHeaderValue(endpointConfiguration));
        }

        return httpHeaders;
    }

    /**
     *
     * @param endpointConfiguration The HttpEndpointConfiguration to get the default request method from
     * @param httpMessage The HttpMessage to override the default with if necessary
     * @return The HttpMethod of the message to send
     */
    private RequestMethod determineRequestMethod(HttpEndpointConfiguration endpointConfiguration,
                                                 HttpMessage httpMessage) {
        RequestMethod method = endpointConfiguration.getRequestMethod();
        if (httpMessage.getRequestMethod() != null) {
            method = httpMessage.getRequestMethod();
        }
        return method;
    }

    /**
     * Composes a HttpEntity based on the given parameters
     * @param httpHeaders The headers to set
     * @param payload The payload to set
     * @param method The HttpMethod to use
     * @return The composed HttpEntitiy
     */
    private HttpEntity createHttpEntity(HttpHeaders httpHeaders, Object payload, RequestMethod method) {
        if (httpMethodSupportsBody(method)) {
            return new HttpEntity<>(payload, httpHeaders);
        } else {
            return new HttpEntity<>(httpHeaders);
        }
    }

    /**
     * Converts the outbound Message object into a HttpMessage
     * @param message The message to convert
     * @return The converted message as HttpMessage
     */
    private HttpMessage convertOutboundMessage(Message message) {
        HttpMessage httpMessage;
        if (message instanceof HttpMessage) {
            httpMessage = (HttpMessage) message;
        } else {
            httpMessage = new HttpMessage(message);
        }
        return httpMessage;
    }

    /**
     * Determines whether the given message type supports a message body
     * @param method The HttpMethod to evaluate
     * @return Whether a message body is supported
     */
    private boolean httpMethodSupportsBody(RequestMethod method) {
        return RequestMethod.POST.equals(method) || RequestMethod.PUT.equals(method)
                || RequestMethod.DELETE.equals(method) || RequestMethod.PATCH.equals(method);
    }

    /**
     * Creates the content type header value enriched with charset information if possible
     * @param endpointConfiguration The endpoint configuration to get the content type from
     * @return The content type header including charset information
     */
    private String composeContentTypeHeaderValue(HttpEndpointConfiguration endpointConfiguration) {
        return (endpointConfiguration.getContentType().contains("charset") || !StringUtils.hasText(endpointConfiguration.getCharset())) ?
                endpointConfiguration.getContentType() :
                endpointConfiguration.getContentType() + ";charset=" + endpointConfiguration.getCharset();
    }

    /**
     * Extracts the message body from the given HttpEntity or returns a default
     * @param message The message to extract the body from
     * @return The body of the HttpEntity or a default value, if no payload is available
     */
    private Object extractMessageBody(HttpEntity message) {
        return message.getBody() != null ? message.getBody() : "";
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy