org.springframework.integration.http.support.DefaultHttpHeaderMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.integration.http.support.DefaultHttpHeaderMapper.java

Source

/*
 * Copyright 2002-2013 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.springframework.integration.http.support;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.mapping.HeaderMapper;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;

/**
 * Default {@link HeaderMapper} implementation for HTTP.
 *
 * @author Mark Fisher
 * @author Jeremy Grelle
 * @author Oleg Zhurakousky
 * @author Gunnar Hillert
 * @author Gary Russell
 * @author Artem Bilan
 * @since 2.0
 */
public class DefaultHttpHeaderMapper implements HeaderMapper<HttpHeaders>, BeanFactoryAware, InitializingBean {

    private static final Log logger = LogFactory.getLog(DefaultHttpHeaderMapper.class);

    private volatile ConversionService conversionService;

    private volatile BeanFactory beanFactory;

    private static final String ACCEPT = "Accept";

    private static final String ACCEPT_CHARSET = "Accept-Charset";

    private static final String ACCEPT_ENCODING = "Accept-Encoding";

    private static final String ACCEPT_LANGUAGE = "Accept-Language";

    private static final String ACCEPT_RANGES = "Accept-Ranges";

    private static final String AGE = "Age";

    private static final String ALLOW = "Allow";

    private static final String AUTHORIZATION = "Authorization";

    private static final String CACHE_CONTROL = "Cache-Control";

    private static final String CONNECTION = "Connection";

    private static final String CONTENT_ENCODING = "Content-Encoding";

    private static final String CONTENT_LANGUAGE = "Content-Language";

    private static final String CONTENT_LENGTH = "Content-Length";

    private static final String CONTENT_LOCATION = "Content-Location";

    private static final String CONTENT_MD5 = "Content-MD5";

    private static final String CONTENT_RANGE = "Content-Range";

    private static final String CONTENT_TYPE = "Content-Type";

    public static final String COOKIE = "Cookie";

    private static final String DATE = "Date";

    private static final String ETAG = "ETag";

    private static final String EXPECT = "Expect";

    private static final String EXPIRES = "Expires";

    private static final String FROM = "From";

    private static final String HOST = "Host";

    private static final String IF_MATCH = "If-Match";

    private static final String IF_MODIFIED_SINCE = "If-Modified-Since";

    private static final String IF_NONE_MATCH = "If-None-Match";

    private static final String IF_RANGE = "If-Range";

    private static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";

    private static final String LAST_MODIFIED = "Last-Modified";

    private static final String LOCATION = "Location";

    private static final String MAX_FORWARDS = "Max-Forwards";

    private static final String PRAGMA = "Pragma";

    private static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";

    private static final String PROXY_AUTHORIZATION = "Proxy-Authorization";

    private static final String RANGE = "Range";

    private static final String REFERER = "Referer";

    private static final String REFRESH = "Refresh";

    private static final String RETRY_AFTER = "Retry-After";

    private static final String SERVER = "Server";

    public static final String SET_COOKIE = "Set-Cookie";

    private static final String TE = "TE";

    private static final String TRAILER = "Trailer";

    private static final String UPGRADE = "Upgrade";

    private static final String USER_AGENT = "User-Agent";

    private static final String VARY = "Vary";

    private static final String VIA = "Via";

    private static final String WARNING = "Warning";

    private static final String WWW_AUTHENTICATE = "WWW-Authenticate";

    private static final String[] HTTP_REQUEST_HEADER_NAMES = new String[] { ACCEPT, ACCEPT_CHARSET,
            ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES, AUTHORIZATION, CACHE_CONTROL, CONNECTION,
            CONTENT_LENGTH, CONTENT_TYPE, COOKIE, DATE, EXPECT, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE,
            IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, MAX_FORWARDS, PRAGMA, PROXY_AUTHORIZATION, RANGE, REFERER,
            TE, UPGRADE, USER_AGENT, VIA, WARNING };

    private static String[] HTTP_RESPONSE_HEADER_NAMES = new String[] { ACCEPT_RANGES, AGE, ALLOW, CACHE_CONTROL,
            CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_MD5, CONTENT_RANGE,
            CONTENT_TYPE, DATE, ETAG, EXPIRES, LAST_MODIFIED, LOCATION, PRAGMA, PROXY_AUTHENTICATE, REFRESH,
            RETRY_AFTER, SERVER, SET_COOKIE, TRAILER, VARY, VIA, WARNING, WWW_AUTHENTICATE };

    private static String[] HTTP_REQUEST_HEADER_NAMES_OUTBOUND_EXCLUSIONS = new String[0];

    private static String[] HTTP_RESPONSE_HEADER_NAMES_INBOUND_EXCLUSIONS = new String[] { CONTENT_LENGTH };

    public static final String HTTP_REQUEST_HEADER_NAME_PATTERN = "HTTP_REQUEST_HEADERS";

    public static final String HTTP_RESPONSE_HEADER_NAME_PATTERN = "HTTP_RESPONSE_HEADERS";

    private volatile String[] outboundHeaderNames = new String[0];

    private volatile String[] inboundHeaderNames = new String[0];

    private volatile String[] excludedOutboundStandardRequestHeaderNames = new String[0];

    private volatile String[] excludedInboundStandardResponseHeaderNames = new String[0];

    private volatile String userDefinedHeaderPrefix = "X-";

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    /**
     * Provide the header names that should be mapped to an HTTP request (for outbound adapters)
     * or HTTP response (for inbound adapters) from a Spring Integration Message's headers.
     * The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched.
     * <p>
     * Any non-standard headers will be prefixed with the value specified by
     * {@link DefaultHttpHeaderMapper#setUserDefinedHeaderPrefix(String)}. The default is 'X-'.
     */
    public void setOutboundHeaderNames(String[] outboundHeaderNames) {
        this.outboundHeaderNames = (outboundHeaderNames != null) ? outboundHeaderNames : new String[0];
    }

    /**
     * Provide the header names that should be mapped from an HTTP request (for inbound adapters)
     * or HTTP response (for outbound adapters) to a Spring Integration Message's headers.
     * The values can also contain simple wildcard patterns (e.g. "foo*" or "*foo") to be matched.
     * <p>
     * This will match the header name directly or, for non-standard HTTP headers, it will match
     * the header name prefixed with the value specified by
     * {@link DefaultHttpHeaderMapper#setUserDefinedHeaderPrefix(String)}. The default is 'X-'.
     */
    public void setInboundHeaderNames(String[] inboundHeaderNames) {
        this.inboundHeaderNames = (inboundHeaderNames != null) ? inboundHeaderNames : new String[0];
    }

    /**
     * Provide header names from the list of standard headers that should be suppressed when
     * mapping outbound endpoint request headers.
     * @param excludedOutboundStandardRequestHeaderNames the excludedStandardRequestHeaderNames to set
     */
    public void setExcludedOutboundStandardRequestHeaderNames(String[] excludedOutboundStandardRequestHeaderNames) {
        Assert.notNull(excludedOutboundStandardRequestHeaderNames,
                "'excludedOutboundStandardRequestHeaderNames' must not be null");
        this.excludedOutboundStandardRequestHeaderNames = excludedOutboundStandardRequestHeaderNames;
    }

    /**
     * Provide header names from the list of standard headers that should be suppressed when
     * mapping inbound endopoint response headers.
     * @param excludedInboundStandardResponseHeaderNames the excludedStandardResponseHeaderNames to set
     */
    public void setExcludedInboundStandardResponseHeaderNames(String[] excludedInboundStandardResponseHeaderNames) {
        Assert.notNull(excludedInboundStandardResponseHeaderNames,
                "'excludedInboundStandardResponseHeaderNames' must not be null");
        this.excludedInboundStandardResponseHeaderNames = excludedInboundStandardResponseHeaderNames;
    }

    /**
     * Sets the prefix to use with user-defined (non-standard) headers. Default is 'X-'.
     */
    public void setUserDefinedHeaderPrefix(String userDefinedHeaderPrefix) {
        this.userDefinedHeaderPrefix = (userDefinedHeaderPrefix != null) ? userDefinedHeaderPrefix : "";
    }

    /**
     * Map from the integration MessageHeaders to an HttpHeaders instance.
     * Depending on which type of adapter is using this mapper, the HttpHeaders might be
     * for an HTTP request (outbound adapter) or for an HTTP response (inbound adapter).
     */
    public void fromHeaders(MessageHeaders headers, HttpHeaders target) {
        if (logger.isDebugEnabled()) {
            logger.debug(MessageFormat.format("outboundHeaderNames={0}",
                    CollectionUtils.arrayToList(outboundHeaderNames)));
        }
        Set<String> headerNames = headers.keySet();
        for (String name : headerNames) {
            if (this.shouldMapOutboundHeader(name)) {
                Object value = headers.get(name);
                if (value != null) {
                    if (!this.containsElementIgnoreCase(HTTP_REQUEST_HEADER_NAMES, name)
                            && !this.containsElementIgnoreCase(HTTP_RESPONSE_HEADER_NAMES, name)) {
                        // prefix the user-defined header names if not already prefixed

                        name = StringUtils.startsWithIgnoreCase(name, this.userDefinedHeaderPrefix) ? name
                                : this.userDefinedHeaderPrefix + name;
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value));
                    }
                    this.setHttpHeader(target, name, value);
                }
            }
        }
    }

    /**
     * Map from an HttpHeaders instance to integration MessageHeaders.
     * Depending on which type of adapter is using this mapper, the HttpHeaders might be
     * from an HTTP request (inbound adapter) or from an HTTP response (outbound adapter).
     */
    public Map<String, Object> toHeaders(HttpHeaders source) {
        if (logger.isDebugEnabled()) {
            logger.debug(MessageFormat.format("inboundHeaderNames={0}",
                    CollectionUtils.arrayToList(inboundHeaderNames)));
        }
        Map<String, Object> target = new HashMap<String, Object>();
        Set<String> headerNames = source.keySet();
        for (String name : headerNames) {
            if (this.shouldMapInboundHeader(name)) {
                if (!ObjectUtils.containsElement(HTTP_REQUEST_HEADER_NAMES, name)
                        && !ObjectUtils.containsElement(HTTP_RESPONSE_HEADER_NAMES, name)) {
                    String prefixedName = StringUtils.startsWithIgnoreCase(name, this.userDefinedHeaderPrefix)
                            ? name
                            : this.userDefinedHeaderPrefix + name;
                    Object value = source.containsKey(prefixedName) ? this.getHttpHeader(source, prefixedName)
                            : this.getHttpHeader(source, name);
                    if (value != null) {
                        if (logger.isDebugEnabled()) {
                            logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value));
                        }
                        this.setMessageHeader(target, name, value);
                    }
                } else {
                    Object value = this.getHttpHeader(source, name);
                    if (value != null) {
                        if (logger.isDebugEnabled()) {
                            logger.debug(MessageFormat.format("setting headerName=[{0}], value={1}", name, value));
                        }
                        this.setMessageHeader(target, name, value);
                    }
                }
            }
        }
        return target;
    }

    public void afterPropertiesSet() throws Exception {
        if (this.beanFactory != null) {
            this.conversionService = IntegrationContextUtils.getConversionService(this.beanFactory);
        }
    }

    private boolean containsElementIgnoreCase(String[] headerNames, String name) {
        for (String headerName : headerNames) {
            if (headerName.equalsIgnoreCase(name)) {
                return true;
            }
        }
        return false;
    }

    private boolean shouldMapOutboundHeader(String headerName) {
        if (this.outboundHeaderNames == HTTP_RESPONSE_HEADER_NAMES) { // a default inbound mapper
            /*
             * When using the default response header name list, suppress the
             * mapping of exclusions for specific headers.
             */
            if (this.containsElementIgnoreCase(this.excludedInboundStandardResponseHeaderNames, headerName)) {
                if (logger.isDebugEnabled()) {
                    logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName));
                }
                return false;
            }
        } else if (this.outboundHeaderNames == HTTP_REQUEST_HEADER_NAMES) { // a default outbound mapper
            /*
             * When using the default request header name list, suppress the
             * mapping of exclusions for specific headers.
             */
            if (this.containsElementIgnoreCase(this.excludedOutboundStandardRequestHeaderNames, headerName)) {
                if (logger.isDebugEnabled()) {
                    logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName));
                }
                return false;
            }
        }
        return this.shouldMapHeader(headerName, this.outboundHeaderNames);
    }

    private boolean shouldMapInboundHeader(String headerName) {
        return this.shouldMapHeader(headerName, this.inboundHeaderNames);
    }

    private boolean shouldMapHeader(String headerName, String[] patterns) {
        if (patterns != null && patterns.length > 0) {
            for (String pattern : patterns) {
                if (PatternMatchUtils.simpleMatch(pattern.toLowerCase(), headerName.toLowerCase())) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}",
                                headerName, pattern));
                    }
                    return true;
                } else if (HTTP_REQUEST_HEADER_NAME_PATTERN.equals(pattern)
                        && this.containsElementIgnoreCase(HTTP_REQUEST_HEADER_NAMES, headerName)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}",
                                headerName, pattern));
                    }
                    return true;
                } else if (HTTP_RESPONSE_HEADER_NAME_PATTERN.equals(pattern)
                        && this.containsElementIgnoreCase(HTTP_RESPONSE_HEADER_NAMES, headerName)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug(MessageFormat.format("headerName=[{0}] WILL be mapped, matched pattern={1}",
                                headerName, pattern));
                    }
                    return true;
                }
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug(MessageFormat.format("headerName=[{0}] WILL NOT be mapped", headerName));
        }
        return false;
    }

    private void setHttpHeader(HttpHeaders target, String name, Object value) {
        if (ACCEPT.equalsIgnoreCase(name)) {
            if (value instanceof Collection<?>) {
                Collection<?> values = (Collection<?>) value;
                if (!CollectionUtils.isEmpty(values)) {
                    List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
                    for (Object type : values) {
                        if (type instanceof MediaType) {
                            acceptableMediaTypes.add((MediaType) type);
                        } else if (type instanceof String) {
                            acceptableMediaTypes.addAll(MediaType.parseMediaTypes((String) type));
                        } else {
                            Class<?> clazz = (type != null) ? type.getClass() : null;
                            throw new IllegalArgumentException(
                                    "Expected MediaType or String value for 'Accept' header value, but received: "
                                            + clazz);
                        }
                    }
                    target.setAccept(acceptableMediaTypes);
                }
            } else if (value instanceof MediaType) {
                target.setAccept(Collections.singletonList((MediaType) value));
            } else if (value instanceof String[]) {
                List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
                for (String next : (String[]) value) {
                    acceptableMediaTypes.add(MediaType.parseMediaType(next));
                }
                target.setAccept(acceptableMediaTypes);
            } else if (value instanceof String) {
                target.setAccept(MediaType.parseMediaTypes((String) value));
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected MediaType or String value for 'Accept' header value, but received: " + clazz);
            }
        } else if (ACCEPT_CHARSET.equalsIgnoreCase(name)) {
            if (value instanceof Collection<?>) {
                Collection<?> values = (Collection<?>) value;
                if (!CollectionUtils.isEmpty(values)) {
                    List<Charset> acceptableCharsets = new ArrayList<Charset>();
                    for (Object charset : values) {
                        if (charset instanceof Charset) {
                            acceptableCharsets.add((Charset) charset);
                        } else if (charset instanceof String) {
                            acceptableCharsets.add(Charset.forName((String) charset));
                        } else {
                            Class<?> clazz = (charset != null) ? charset.getClass() : null;
                            throw new IllegalArgumentException(
                                    "Expected Charset or String value for 'Accept-Charset' header value, but received: "
                                            + clazz);
                        }
                    }
                    target.setAcceptCharset(acceptableCharsets);
                }
            } else if (value instanceof Charset[] || value instanceof String[]) {
                List<Charset> acceptableCharsets = new ArrayList<Charset>();
                Object[] values = ObjectUtils.toObjectArray(value);
                for (Object charset : values) {
                    if (charset instanceof Charset) {
                        acceptableCharsets.add((Charset) charset);
                    } else if (charset instanceof String) {
                        acceptableCharsets.add(Charset.forName((String) charset));
                    }
                }
                target.setAcceptCharset(acceptableCharsets);
            } else if (value instanceof Charset) {
                target.setAcceptCharset(Collections.singletonList((Charset) value));
            } else if (value instanceof String) {
                String[] charsets = StringUtils.commaDelimitedListToStringArray((String) value);
                List<Charset> acceptableCharsets = new ArrayList<Charset>();
                for (String charset : charsets) {
                    acceptableCharsets.add(Charset.forName(charset.trim()));
                }
                target.setAcceptCharset(acceptableCharsets);
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected Charset or String value for 'Accept-Charset' header value, but received: "
                                + clazz);
            }
        } else if (ALLOW.equalsIgnoreCase(name)) {
            if (value instanceof Collection<?>) {
                Collection<?> values = (Collection<?>) value;
                if (!CollectionUtils.isEmpty(values)) {
                    Set<HttpMethod> allowedMethods = new HashSet<HttpMethod>();
                    for (Object method : values) {
                        if (method instanceof HttpMethod) {
                            allowedMethods.add((HttpMethod) method);
                        } else if (method instanceof String) {
                            allowedMethods.add(HttpMethod.valueOf((String) method));
                        } else {
                            Class<?> clazz = (method != null) ? method.getClass() : null;
                            throw new IllegalArgumentException(
                                    "Expected HttpMethod or String value for 'Allow' header value, but received: "
                                            + clazz);
                        }
                    }
                    target.setAllow(allowedMethods);
                }
            } else {
                if (value instanceof HttpMethod) {
                    target.setAllow(Collections.singleton((HttpMethod) value));
                } else if (value instanceof HttpMethod[]) {
                    Set<HttpMethod> allowedMethods = new HashSet<HttpMethod>();
                    for (HttpMethod next : (HttpMethod[]) value) {
                        allowedMethods.add(next);
                    }
                    target.setAllow(allowedMethods);
                } else if (value instanceof String || value instanceof String[]) {
                    String[] values = (value instanceof String[]) ? (String[]) value
                            : StringUtils.commaDelimitedListToStringArray((String) value);
                    Set<HttpMethod> allowedMethods = new HashSet<HttpMethod>();
                    for (String next : values) {
                        allowedMethods.add(HttpMethod.valueOf(next.trim()));
                    }
                    target.setAllow(allowedMethods);
                } else {
                    Class<?> clazz = (value != null) ? value.getClass() : null;
                    throw new IllegalArgumentException(
                            "Expected HttpMethod or String value for 'Allow' header value, but received: " + clazz);
                }
            }
        } else if (CACHE_CONTROL.equalsIgnoreCase(name)) {
            if (value instanceof String) {
                target.setCacheControl((String) value);
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected String value for 'Cache-Control' header value, but received: " + clazz);
            }
        } else if (CONTENT_LENGTH.equalsIgnoreCase(name)) {
            if (value instanceof Number) {
                target.setContentLength(((Number) value).longValue());
            } else if (value instanceof String) {
                target.setContentLength(Long.parseLong((String) value));
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected Number or String value for 'Content-Length' header value, but received: "
                                + clazz);
            }
        } else if (CONTENT_TYPE.equalsIgnoreCase(name)) {
            if (value instanceof MediaType) {
                target.setContentType((MediaType) value);
            } else if (value instanceof String) {
                target.setContentType(MediaType.parseMediaType((String) value));
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected MediaType or String value for 'Content-Type' header value, but received: "
                                + clazz);
            }
        } else if (DATE.equalsIgnoreCase(name)) {
            if (value instanceof Date) {
                target.setDate(((Date) value).getTime());
            } else if (value instanceof Number) {
                target.setDate(((Number) value).longValue());
            } else if (value instanceof String) {
                target.setDate(Long.parseLong((String) value));
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected Date, Number, or String value for 'Date' header value, but received: " + clazz);
            }
        } else if (ETAG.equalsIgnoreCase(name)) {
            if (value instanceof String) {
                target.setETag((String) value);
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected String value for 'ETag' header value, but received: " + clazz);
            }
        } else if (EXPIRES.equalsIgnoreCase(name)) {
            if (value instanceof Date) {
                target.setExpires(((Date) value).getTime());
            } else if (value instanceof Number) {
                target.setExpires(((Number) value).longValue());
            } else if (value instanceof String) {
                target.setExpires(Long.parseLong((String) value));
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected Date, Number, or String value for 'Expires' header value, but received: "
                                + clazz);
            }
        } else if (IF_MODIFIED_SINCE.equalsIgnoreCase(name)) {
            if (value instanceof Date) {
                target.setIfModifiedSince(((Date) value).getTime());
            } else if (value instanceof Number) {
                target.setIfModifiedSince(((Number) value).longValue());
            } else if (value instanceof String) {
                target.setIfModifiedSince(Long.parseLong((String) value));
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected Date, Number, or String value for 'If-Modified-Since' header value, but received: "
                                + clazz);
            }
        } else if (IF_NONE_MATCH.equalsIgnoreCase(name)) {
            if (value instanceof String) {
                target.setIfNoneMatch((String) value);
            } else if (value instanceof String[]) {
                String delmitedString = StringUtils.arrayToCommaDelimitedString((String[]) value);
                target.setIfNoneMatch(delmitedString);
            } else if (value instanceof Collection) {
                Collection<?> values = (Collection<?>) value;
                if (!CollectionUtils.isEmpty(values)) {
                    List<String> ifNoneMatchList = new ArrayList<String>();
                    for (Object next : values) {
                        if (next instanceof String) {
                            ifNoneMatchList.add((String) next);
                        } else {
                            Class<?> clazz = (next != null) ? next.getClass() : null;
                            throw new IllegalArgumentException(
                                    "Expected String value for 'If-None-Match' header value, but received: "
                                            + clazz);
                        }
                    }
                    target.setIfNoneMatch(ifNoneMatchList);
                }
            }
        } else if (LAST_MODIFIED.equalsIgnoreCase(name)) {
            if (value instanceof Date) {
                target.setLastModified(((Date) value).getTime());
            } else if (value instanceof Number) {
                target.setLastModified(((Number) value).longValue());
            } else if (value instanceof String) {
                target.setLastModified(Long.parseLong((String) value));
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected Date, Number, or String value for 'Last-Modified' header value, but received: "
                                + clazz);
            }
        } else if (LOCATION.equalsIgnoreCase(name)) {
            if (value instanceof URI) {
                target.setLocation((URI) value);
            } else if (value instanceof String) {
                try {
                    target.setLocation(new URI((String) value));
                } catch (URISyntaxException e) {
                    throw new IllegalArgumentException(e);
                }
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected URI or String value for 'Location' header value, but received: " + clazz);
            }
        } else if (PRAGMA.equalsIgnoreCase(name)) {
            if (value instanceof String) {
                target.setPragma((String) value);
            } else {
                Class<?> clazz = (value != null) ? value.getClass() : null;
                throw new IllegalArgumentException(
                        "Expected String value for 'Pragma' header value, but received: " + clazz);
            }
        } else if (value instanceof String) {
            target.set(name, (String) value);
        } else if (value instanceof String[]) {
            for (String next : (String[]) value) {
                target.add(name, next);
            }
        } else if (value instanceof Iterable<?>) {
            for (Object next : (Iterable<?>) value) {
                String convertedValue = null;
                if (next instanceof String) {
                    convertedValue = (String) next;
                } else {
                    convertedValue = this.convertToString(value);
                }
                if (StringUtils.hasText(convertedValue)) {
                    target.add(name, (String) next);
                } else {
                    logger.warn("Element of the header '" + name + "' with value '" + value
                            + "' will not be set since it is not a String and no Converter "
                            + "is available. Consider registering a Converter with ConversionService (e.g., <int:converter>)");
                }
            }
        } else {
            String convertedValue = this.convertToString(value);
            if (StringUtils.hasText(convertedValue)) {
                target.set(name, convertedValue);
            } else {
                logger.warn("Header '" + name + "' with value '" + value
                        + "' will not be set since it is not a String and no Converter "
                        + "is available. Consider registering a Converter with ConversionService (e.g., <int:converter>)");
            }
        }
    }

    private Object getHttpHeader(HttpHeaders source, String name) {
        if (ACCEPT.equalsIgnoreCase(name)) {
            return source.getAccept();
        } else if (ACCEPT_CHARSET.equalsIgnoreCase(name)) {
            return source.getAcceptCharset();
        } else if (ALLOW.equalsIgnoreCase(name)) {
            return source.getAllow();
        } else if (CACHE_CONTROL.equalsIgnoreCase(name)) {
            String cacheControl = source.getCacheControl();
            return (StringUtils.hasText(cacheControl)) ? cacheControl : null;
        } else if (CONTENT_LENGTH.equalsIgnoreCase(name)) {
            long contentLength = source.getContentLength();
            return (contentLength > -1) ? contentLength : null;
        } else if (CONTENT_TYPE.equalsIgnoreCase(name)) {
            return source.getContentType();
        } else if (DATE.equalsIgnoreCase(name)) {
            long date = source.getDate();
            return (date > -1) ? date : null;
        } else if (ETAG.equalsIgnoreCase(name)) {
            String eTag = source.getETag();
            return (StringUtils.hasText(eTag)) ? eTag : null;
        } else if (EXPIRES.equalsIgnoreCase(name)) {
            try {
                long expires = source.getExpires();
                return (expires > -1) ? expires : null;
            } catch (Exception e) {
                if (logger.isDebugEnabled()) {
                    logger.debug(e.getMessage());
                }
                // According to RFC 2616
                return null;
            }
        } else if (IF_NONE_MATCH.equalsIgnoreCase(name)) {
            return source.getIfNoneMatch();
        } else if (IF_UNMODIFIED_SINCE.equalsIgnoreCase(name)) {
            long unmodifiedSince = source.getIfNotModifiedSince();
            return (unmodifiedSince > -1) ? unmodifiedSince : null;
        } else if (LAST_MODIFIED.equalsIgnoreCase(name)) {
            long lastModified = source.getLastModified();
            return (lastModified > -1) ? lastModified : null;
        } else if (LOCATION.equalsIgnoreCase(name)) {
            return source.getLocation();
        } else if (PRAGMA.equalsIgnoreCase(name)) {
            String pragma = source.getPragma();
            return (StringUtils.hasText(pragma)) ? pragma : null;
        }
        return source.get(name);
    }

    private void setMessageHeader(Map<String, Object> target, String name, Object value) {
        if (ObjectUtils.isArray(value)) {
            Object[] values = ObjectUtils.toObjectArray(value);
            if (!ObjectUtils.isEmpty(values)) {
                if (values.length == 1) {
                    target.put(name, values);
                } else {
                    target.put(name, values[0]);
                }
            }
        } else if (value instanceof Collection<?>) {
            Collection<?> values = (Collection<?>) value;
            if (!CollectionUtils.isEmpty(values)) {
                if (values.size() == 1) {
                    target.put(name, values.iterator().next());
                } else {
                    target.put(name, values);
                }
            }
        } else if (value != null) {
            target.put(name, value);
        }
    }

    private String convertToString(Object value) {
        if (this.conversionService != null && this.conversionService.canConvert(TypeDescriptor.forObject(value),
                TypeDescriptor.valueOf(String.class))) {
            return this.conversionService.convert(value, String.class);
        }
        return null;
    }

    /**
     * Factory method for creating a basic outbound mapper instance.
     * This will map all standard HTTP request headers when sending an HTTP request,
     * and it will map all standard HTTP response headers when receiving an HTTP response.
     */
    public static DefaultHttpHeaderMapper outboundMapper() {
        DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper();
        mapper.setOutboundHeaderNames(HTTP_REQUEST_HEADER_NAMES);
        mapper.setInboundHeaderNames(HTTP_RESPONSE_HEADER_NAMES);
        mapper.setExcludedOutboundStandardRequestHeaderNames(HTTP_REQUEST_HEADER_NAMES_OUTBOUND_EXCLUSIONS);
        return mapper;
    }

    /**
     * Factory method for creating a basic inbound mapper instance.
     * This will map all standard HTTP request headers when receiving an HTTP request,
     * and it will map all standard HTTP response headers when sending an HTTP response.
     */
    public static DefaultHttpHeaderMapper inboundMapper() {
        DefaultHttpHeaderMapper mapper = new DefaultHttpHeaderMapper();
        mapper.setInboundHeaderNames(HTTP_REQUEST_HEADER_NAMES);
        mapper.setOutboundHeaderNames(HTTP_RESPONSE_HEADER_NAMES);
        mapper.setExcludedInboundStandardResponseHeaderNames(HTTP_RESPONSE_HEADER_NAMES_INBOUND_EXCLUSIONS);
        return mapper;
    }
}