org.orbeon.oxf.util.Connection.java Source code

Java tutorial

Introduction

Here is the source code for org.orbeon.oxf.util.Connection.java

Source

/**
 * Copyright (C) 2009 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.util;

import org.apache.commons.lang.StringUtils;
import org.apache.http.client.CookieStore;
import org.apache.log4j.Level;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.pipeline.api.ExternalContext;
import org.orbeon.oxf.properties.PropertySet;
import org.orbeon.oxf.resources.handler.HTTPURLConnection;
import org.orbeon.oxf.xml.XMLUtils;
import org.orbeon.oxf.xml.dom4j.LocationData;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

public class Connection {

    public static class Credentials {
        public final String username;
        public final String password;
        public final String preemptiveAuthentication;
        public final String domain;

        public Credentials(String username, String password, String preemptiveAuthentication, String domain) {

            assert username != null;

            this.username = username;
            this.password = password;
            this.preemptiveAuthentication = preemptiveAuthentication;
            this.domain = domain;
        }

        public String getPrefix() {
            if (password != null)
                return username + ":" + password + "@";
            else
                return username + "@";
        }

        @Override
        public String toString() {
            return "[" + username + ", " + password + ", " + preemptiveAuthentication + ", " + domain + "]";
        }
    }

    // NOTE: Could add a distinction for portlet session scope.
    public enum StateScope {
        NONE, REQUEST, SESSION, APPLICATION
    }

    public enum Method {
        GET, PUT, POST
    }

    public static final String AUTHORIZATION_HEADER = "Authorization";

    private static final StateScope DEFAULT_STATE_SCOPE = StateScope.SESSION;
    private static final String LOG_TYPE = "connection";

    public static final String HTTP_FORWARD_HEADERS_PROPERTY = "oxf.http.forward-headers";
    public static final String HTTP_FORWARD_COOKIES_PROPERTY = "oxf.http.forward-cookies";

    public static final String HTTP_STATE_PROPERTY = "oxf.http.state";
    private static final String HTTP_COOKIE_STORE_ATTRIBUTE = "oxf.http.cookie-store";

    private final StateScope stateScope = getStateScope();

    private CookieStore cookieStore;

    private boolean isHTTPOrHTTPS;
    private IndentedLogger indentedLogger;
    private boolean logBody;
    private String httpMethod;
    private URL connectionURL;
    private Credentials credentials;
    String contentType;
    private byte[] messageBody;
    private Map<String, String[]> headersMap;

    /**
     * Perform a connection to the given URL with the given parameters.
     *
     * This handles:
     *
     * o PUTting or POSTing a body
     * o handling credentials
     * o setting HTTP headers
     * o forwarding session cookies
     * o forwarding specified HTTP headers
     * o managing SOAP POST and GET a la XForms 1.1 (should this be here?)
     */
    public ConnectionResult open(ExternalContext externalContext, IndentedLogger indentedLogger, boolean logBody,
            String httpMethod, final URL connectionURL, Credentials credentials, String contentType,
            byte[] messageBody, Map<String, String[]> headerNameValues, String headersToForward) {

        indentedLogger.startHandleOperation(LOG_TYPE, "opening connection");
        try {
            // Prepare, connect, and cleanup
            prepare(externalContext, indentedLogger, logBody, httpMethod, connectionURL, credentials, contentType,
                    messageBody, headerNameValues, headersToForward, true);
            final ConnectionResult result = connect();
            cleanup(externalContext, true);

            return result;
        } finally {
            // In case an exception is thrown in the body, still do adjust the logs
            indentedLogger.endHandleOperation();
        }
    }

    public Connection prepare(ExternalContext externalContext, IndentedLogger indentedLogger, boolean logBody,
            String httpMethod, final URL connectionURL, Credentials credentials, String contentType,
            byte[] messageBody, Map<String, String[]> headerNameValues, String headersToForward,
            boolean handleState) {

        isHTTPOrHTTPS = isHTTPOrHTTPS(connectionURL.getProtocol());

        // Caller might pass null here
        if (headerNameValues == null)
            headerNameValues = Collections.emptyMap();

        // Get state if possible
        if (handleState && isHTTPOrHTTPS)
            loadHttpState(externalContext, indentedLogger);

        this.indentedLogger = indentedLogger;
        this.logBody = logBody;
        this.httpMethod = httpMethod;
        this.connectionURL = connectionURL;
        this.credentials = credentials;
        this.contentType = contentType;
        this.messageBody = messageBody;

        // Get  the headers to forward if any
        headersMap = (externalContext.getRequest() != null)
                ? getHeadersMap(externalContext, indentedLogger, credentials, headerNameValues, headersToForward)
                : headerNameValues;

        return this;
    }

    public void cleanup(ExternalContext externalContext, boolean handleState) {
        // Save state if possible
        if (handleState && isHTTPOrHTTPS)
            saveHttpState(externalContext, indentedLogger);
    }

    /**
     * Get header names and values to send given:
     *
     * o the incoming request
     * o a list of headers names and values to set
     * o credentials information
     * o a list of headers to forward
     *
     * @param externalContext   context
     * @param indentedLogger    logger or null
     * @param credentials       credentials or null
     * @param headerNameValues  LinkedHashMap<String headerName, String[] headerValues> or null
     * @param headersToForward  headers to forward or null
     * @return LinkedHashMap<String headerName, String[] headerValues>
     */
    public static Map<String, String[]> getHeadersMap(ExternalContext externalContext,
            IndentedLogger indentedLogger, Credentials credentials, Map<String, String[]> headerNameValues,
            String headersToForward) {

        final boolean doLog = (indentedLogger != null && indentedLogger.isDebugEnabled());

        // Resulting header names and values to set
        final LinkedHashMap<String, String[]> headersMap = new LinkedHashMap<String, String[]>();

        // Get header forwarding information
        final Map<String, String> headersToForwardMap = getHeadersToForward(headersToForward);

        // Set headers if provided
        if (headerNameValues != null && headerNameValues.size() > 0) {
            for (final Map.Entry<String, String[]> currentEntry : headerNameValues.entrySet()) {
                final String currentHeaderName = currentEntry.getKey();
                final String currentHeaderNameLowercase = currentHeaderName.toLowerCase();
                final String[] currentHeaderValues = currentEntry.getValue();
                // Set header
                headersMap.put(currentHeaderNameLowercase, currentHeaderValues);
                // Remove from list of headers to forward below
                if (headersToForwardMap != null)
                    headersToForwardMap.remove(currentHeaderNameLowercase);
            }
        }

        // Forward cookies for session handling
        // NOTE: We use a property, as some app servers like WebLogic allow configuring the session cookie name.
        final String[] cookiesToForward = getForwardCookies();
        if (credentials == null && cookiesToForward.length > 0) {

            // NOTES 2011-01-22:
            //
            // If this is requested when a page is generated, it turns out we cannot rely on a JSESSIONID that makes
            // sense right after authentication, even in the scenario where the JSESSIONID is clean, because Tomcat
            // replays the initial request. In other words the JSESSIONID cookie can be stale.
            //
            // This means that the forwarding done below often doesn't make sense.
            //
            // We could possibly allow it only for XForms Ajax/page updates, where the probability that JSESSIONID is
            // correct is greater.
            //
            // A stronger fix might be to simply disable JSESSIONID forwarding, or support a stronger SSO option.
            //
            // See: http://forge.ow2.org/tracker/?func=detail&atid=350207&aid=315104&group_id=168
            //      https://issues.apache.org/bugzilla/show_bug.cgi?id=50633
            //

            // START "NEW" 2009 ALGORITHM

            // By convention, the first cookie name is the session cookie
            final String sessionCookieName = cookiesToForward[0];

            // 1. If there is an incoming JSESSIONID cookie, use it. The reason is that there is not necessarily an
            // obvious mapping between "session id" and JSESSIONID cookie value. With Tomcat, this works, but with e.g.
            // WebSphere, you get session id="foobar" and JSESSIONID=0000foobar:-1. So we must first try to get the
            // incoming JSESSIONID. To do this, we get the cookie, then serialize it as a header.

            // TODO: ExternalContext must provide direct access to cookies
            final Object nativeRequest = externalContext.getRequest().getNativeRequest();
            boolean sessionCookieSet = false;
            if (nativeRequest instanceof HttpServletRequest) {
                final Cookie[] cookies = ((HttpServletRequest) nativeRequest).getCookies();

                final StringBuilder sb = new StringBuilder();

                if (cookies != null) {

                    // Figure out if we need to forward session cookies. We only forward if there is the requested
                    // session id is the same as the current session. Otherwise, it means that the current session is no
                    // longer valid, or that the incoming cookie is out of date.
                    boolean forwardSessionCookies = false;
                    final ExternalContext.Session session = externalContext.getSession(false);
                    if (session != null) {
                        final String requestedSessionId = externalContext.getRequest().getRequestedSessionId();
                        if (session.getId().equals(requestedSessionId)) {
                            forwardSessionCookies = true;
                        }
                    }

                    if (forwardSessionCookies) {

                        final List<String> cookiesToForwardAsList = Arrays.asList(cookiesToForward);

                        for (final Cookie cookie : cookies) {
                            // Remember if we've seen the session cookie
                            sessionCookieSet |= cookie.getName().equals(sessionCookieName);

                            if (cookiesToForwardAsList.contains(cookie.getName())) {
                                // Multiple cookies in the header, separated with ";"
                                if (sb.length() > 0)
                                    sb.append("; ");

                                sb.append(cookie.getName());
                                sb.append('=');
                                sb.append(cookie.getValue());
                            }
                        }

                        if (sb.length() > 0) {
                            // One or more cookies were set
                            final String cookieString = sb.toString();
                            if (doLog)
                                indentedLogger.logDebug(LOG_TYPE, "forwarding cookies", "cookie", cookieString,
                                        "requested session id",
                                        externalContext.getRequest().getRequestedSessionId());
                            StringConversions.addValueToStringArrayMap(headersMap, "cookie", cookieString);
                        }
                    }
                }
            }

            // 2. If there is no incoming session cookie, try to make our own cookie. This may fail with e.g.
            // WebSphere.
            if (!sessionCookieSet) {
                final ExternalContext.Session session = externalContext.getSession(false);

                if (session != null) {

                    // This will work with Tomcat, but may not work with other app servers
                    StringConversions.addValueToStringArrayMap(headersMap, "cookie",
                            sessionCookieName + "=" + session.getId());

                    // All this is for logging!
                    if (doLog) {

                        String incomingSessionHeader = null;
                        final String[] cookieHeaders = externalContext.getRequest().getHeaderValuesMap()
                                .get("cookie");
                        if (cookieHeaders != null) {
                            for (final String cookie : cookieHeaders) {
                                if (cookie.contains(sessionCookieName)) {
                                    incomingSessionHeader = cookie;
                                }
                            }
                        }

                        String incomingSessionCookie = null;
                        if (nativeRequest instanceof HttpServletRequest) {
                            final Cookie[] cookies = ((HttpServletRequest) externalContext.getRequest()
                                    .getNativeRequest()).getCookies();
                            if (cookies != null) {
                                for (final Cookie cookie : cookies) {
                                    if (cookie.getName().equals(sessionCookieName)) {
                                        incomingSessionCookie = cookie.getValue();
                                    }
                                }
                            }
                        }

                        indentedLogger.logDebug(LOG_TYPE, "setting cookie", "new session",
                                Boolean.toString(session.isNew()), "session id", session.getId(),
                                "requested session id", externalContext.getRequest().getRequestedSessionId(),
                                "session cookie name", sessionCookieName, "incoming session cookie",
                                incomingSessionCookie, "incoming session header", incomingSessionHeader);
                    }
                }
            }

            // END "NEW" 2009 ALGORITHM
        }

        // Forward headers if needed
        // NOTE: Forwarding the "Cookie" header may yield unpredictable results because of the above work done w/ session cookies
        if (headersToForwardMap != null) {

            final Map<String, String[]> requestHeaderValuesMap = externalContext.getRequest().getHeaderValuesMap();

            for (final Map.Entry<String, String> currentEntry : headersToForwardMap.entrySet()) {
                final String currentHeaderNameLowercase = currentEntry.getKey();

                // Get incoming header value (Map contains values in lowercase!)
                final String[] currentIncomingHeaderValues = requestHeaderValuesMap.get(currentHeaderNameLowercase);
                // Forward header if present
                if (currentIncomingHeaderValues != null) {
                    final boolean isAuthorizationHeader = currentHeaderNameLowercase
                            .equalsIgnoreCase(Connection.AUTHORIZATION_HEADER);
                    if (!isAuthorizationHeader || isAuthorizationHeader && credentials == null) {
                        // Only forward Authorization header if there is no credentials provided
                        if (doLog)
                            indentedLogger.logDebug(LOG_TYPE, "forwarding header", "name",
                                    currentHeaderNameLowercase, "value",
                                    StringUtils.join(currentIncomingHeaderValues, ' '));
                        StringConversions.addValuesToStringArrayMap(headersMap, currentHeaderNameLowercase,
                                currentIncomingHeaderValues);
                    } else {
                        // Just log this information
                        if (doLog)
                            indentedLogger.logDebug(LOG_TYPE,
                                    "not forwarding Authorization header because credentials are present");
                    }
                }
            }
        }

        return headersMap;
    }

    private void loadHttpState(ExternalContext externalContext, IndentedLogger indentedLogger) {

        // NOTE: BasicCookieStore is @ThreadSafe

        switch (stateScope) {
        case REQUEST:
            cookieStore = (CookieStore) externalContext.getRequest().getAttributesMap()
                    .get(HTTP_COOKIE_STORE_ATTRIBUTE);
            break;
        case SESSION:
            final ExternalContext.Session session = externalContext.getSession(false);
            if (session != null)
                cookieStore = (CookieStore) session.getAttributesMap().get(HTTP_COOKIE_STORE_ATTRIBUTE);
            break;
        case APPLICATION:
            cookieStore = (CookieStore) externalContext.getWebAppContext().getAttributesMap()
                    .get(HTTP_COOKIE_STORE_ATTRIBUTE);
            break;
        }

        if (cookieStore != null) {
            indentedLogger.logDebug(LOG_TYPE, "loaded HTTP state", "scope", stateScope.toString().toLowerCase());
        } else {
            indentedLogger.logDebug(LOG_TYPE, "did not load HTTP state");
        }
    }

    private void saveHttpState(ExternalContext externalContext, IndentedLogger indentedLogger) {
        if (cookieStore != null) {
            switch (stateScope) {
            case REQUEST:
                externalContext.getRequest().getAttributesMap().put(HTTP_COOKIE_STORE_ATTRIBUTE, cookieStore);
                break;
            case SESSION:
                final ExternalContext.Session session = externalContext.getSession(false);
                if (session != null)
                    session.getAttributesMap().put(HTTP_COOKIE_STORE_ATTRIBUTE, cookieStore);
                break;
            case APPLICATION:
                externalContext.getWebAppContext().getAttributesMap().put(HTTP_COOKIE_STORE_ATTRIBUTE, cookieStore);
                break;
            }

            if (indentedLogger.isDebugEnabled()) {
                // Log information about state
                if (cookieStore != null) {
                    final StringBuilder sb = new StringBuilder();
                    for (org.apache.http.cookie.Cookie cookie : cookieStore.getCookies()) {
                        if (sb.length() > 0)
                            sb.append(" | ");
                        sb.append(cookie.getName());
                    }
                    indentedLogger.logDebug(LOG_TYPE, "saved HTTP state", "scope",
                            stateScope.toString().toLowerCase(), (sb.length() > 0) ? "cookie names" : null,
                            sb.toString());
                }
            }
        }
    }

    /**
     * Open the connection. This sends request headers, request body, and reads status and response headers.
     *
     * @return                  connection result
     */
    public ConnectionResult connect() {

        final boolean isDebugEnabled = indentedLogger.isDebugEnabled();

        // Perform connection
        final String scheme = connectionURL.getProtocol();
        if (isHTTPOrHTTPS(scheme)
                || (httpMethod.equals("GET") && (scheme.equals("file") || scheme.equals("oxf")))) {
            // http MUST be supported
            // https SHOULD be supported
            // file SHOULD be supported
            try {
                // Create URL connection object
                final URLConnection urlConnection = connectionURL.openConnection();
                final HTTPURLConnection httpURLConnection = (urlConnection instanceof HTTPURLConnection)
                        ? (HTTPURLConnection) urlConnection
                        : null;

                // Whether a message body must be sent
                final boolean hasRequestBody = httpMethod.equals("POST") || httpMethod.equals("PUT");

                urlConnection.setDoInput(true);
                urlConnection.setDoOutput(hasRequestBody);

                // Configure HTTPURLConnection
                if (httpURLConnection != null) {
                    // Set state if possible
                    httpURLConnection.setCookieStore(this.cookieStore);

                    // Set method
                    httpURLConnection.setRequestMethod(httpMethod);

                    // Set credentials
                    if (credentials != null) {

                        httpURLConnection.setUsername(credentials.username);
                        if (credentials.password != null)
                            httpURLConnection.setPassword(credentials.password);
                        if (credentials.preemptiveAuthentication != null)
                            httpURLConnection.setPreemptiveAuthentication(credentials.preemptiveAuthentication);
                        if (credentials.domain != null)
                            httpURLConnection.setDomain(credentials.domain);
                    }
                }

                // Update request headers
                {
                    // Handle SOAP
                    // Set request Content-Type, SOAPAction or Accept header if needed
                    final boolean didSOAP = handleSOAP(indentedLogger, httpMethod, headersMap, contentType,
                            hasRequestBody);

                    // Set request content type
                    if (!didSOAP && hasRequestBody) {
                        final String actualContentType = (contentType != null) ? contentType : "application/xml";
                        headersMap.put("Content-Type", new String[] { actualContentType });
                        indentedLogger.logDebug(LOG_TYPE, "setting header", "Content-Type", actualContentType);
                    }
                }

                // Set headers on connection
                final List<String> headersToLog;
                if (headersMap != null && headersMap.size() > 0) {

                    headersToLog = isDebugEnabled ? new ArrayList<String>() : null;

                    for (Map.Entry<String, String[]> currentEntry : headersMap.entrySet()) {
                        final String currentHeaderName = currentEntry.getKey();
                        final String[] currentHeaderValues = currentEntry.getValue();
                        if (currentHeaderValues != null) {
                            // Add all header values as "request properties"
                            for (String currentHeaderValue : currentHeaderValues) {
                                urlConnection.addRequestProperty(currentHeaderName, currentHeaderValue);

                                if (headersToLog != null) {
                                    headersToLog.add(currentHeaderName);
                                    headersToLog.add(currentHeaderValue);
                                }
                            }
                        }
                    }
                } else {
                    headersToLog = null;
                }

                // Log request details except body
                if (isDebugEnabled) {
                    // Basic connection information
                    final URI connectionURI;
                    try {
                        String userInfo = connectionURL.getUserInfo();
                        if (userInfo != null) {
                            final int colonIndex = userInfo.indexOf(':');
                            if (colonIndex != -1)
                                userInfo = userInfo.substring(0, colonIndex + 1) + "xxxxxxxx";// hide password in logs
                        }
                        connectionURI = new URI(connectionURL.getProtocol(), userInfo, connectionURL.getHost(),
                                connectionURL.getPort(), connectionURL.getPath(), connectionURL.getQuery(),
                                connectionURL.getRef());
                    } catch (URISyntaxException e) {
                        throw new OXFException(e);
                    }
                    indentedLogger.logDebug(LOG_TYPE, "opening URL connection", "method", httpMethod, "URL",
                            connectionURI.toString(), "request Content-Type", contentType);

                    // Log all headers
                    if (headersToLog != null) {
                        final String[] strings = new String[headersToLog.size()];
                        indentedLogger.logDebug(LOG_TYPE, "request headers", headersToLog.toArray(strings));
                    }
                }

                // Write request body if needed
                if (hasRequestBody) {

                    // Case of empty body
                    if (messageBody == null)
                        messageBody = new byte[0];

                    // Log message body for debugging purposes
                    if (logBody)
                        logRequestBody(indentedLogger, contentType, messageBody);

                    // Set request body on connection
                    httpURLConnection.setRequestBody(messageBody);
                }

                // Connect
                urlConnection.connect();

                if (httpURLConnection != null) {
                    // Get state if possible
                    // This is either the state we set above before calling connect(), or a new state if we didn't provide any
                    this.cookieStore = httpURLConnection.getCookieStore();
                }

                // Create result
                final ConnectionResult connectionResult = new ConnectionResult(connectionURL.toExternalForm()) {
                    @Override
                    public void close() {
                        if (getResponseInputStream() != null) {
                            try {
                                getResponseInputStream().close();
                            } catch (IOException e) {
                                throw new OXFException(
                                        "Exception while closing input stream for action: " + connectionURL);
                            }
                        }

                        if (httpURLConnection != null)
                            httpURLConnection.disconnect();
                    }
                };

                // Get response information that needs to be forwarded
                {
                    // Status code
                    connectionResult.statusCode = (httpURLConnection != null) ? httpURLConnection.getResponseCode()
                            : 200;

                    // Headers
                    connectionResult.responseHeaders = urlConnection.getHeaderFields();
                    connectionResult.setLastModified(NetUtils.getLastModifiedAsLong(urlConnection));

                    // Content-Type
                    connectionResult.setResponseContentType(urlConnection.getContentType(), "application/xml");
                }

                // Log response details except body
                if (isDebugEnabled) {
                    connectionResult.logResponseDetailsIfNeeded(indentedLogger, Level.DEBUG, LOG_TYPE);
                }

                // Response stream
                connectionResult.setResponseInputStream(urlConnection.getInputStream());

                // Log response body
                if (isDebugEnabled) {
                    connectionResult.logResponseBody(indentedLogger, Level.DEBUG, LOG_TYPE, logBody);
                }

                return connectionResult;

            } catch (IOException e) {
                throw new ValidationException(e, new LocationData(connectionURL.toExternalForm(), -1, -1));
            }
        } else if (!httpMethod.equals("GET") && (scheme.equals("file") || scheme.equals("oxf"))) {
            // TODO: implement writing to file: and oxf:
            // SHOULD be supported (should probably support oxf: as well)
            throw new OXFException("submission URL scheme not yet implemented: " + scheme);
        } else if (scheme.equals("mailto")) {
            // TODO: implement sending mail
            // MAY be supported
            throw new OXFException("submission URL scheme not yet implemented: " + scheme);
        } else {
            throw new OXFException("submission URL scheme not supported: " + scheme);
        }
    }

    /**
     * Add SOAP-related headers if needed.
     *
     * TODO: check this logic against the latest XForms 1.1.
     *
     * @param indentedLogger    logger
     * @param httpMethod        method
     * @param headersMap        existing headers
     * @param contentType       content-type requested
     * @param hasRequestBody    whether there is a request body
     * @return                  true iif a SOAP request was detected and headers added
     */
    private boolean handleSOAP(IndentedLogger indentedLogger, String httpMethod, Map<String, String[]> headersMap,
            String contentType, boolean hasRequestBody) {
        final String contentTypeMediaType = NetUtils.getContentTypeMediaType(contentType);
        if (hasRequestBody) {
            if (httpMethod.equals("POST") && NetUtils.APPLICATION_SOAP_XML.equals(contentTypeMediaType)) {
                // SOAP POST

                final Map<String, String> parameters = NetUtils.getContentTypeParameters(contentType);
                final StringBuilder sb = new StringBuilder("text/xml");

                // Extract charset parameter if present
                // TODO: We have the body as bytes already, using the xforms:submission/@encoding attribute, so this is not right.
                if (parameters != null) {
                    final String charsetParameter = parameters.get("charset");
                    if (charsetParameter != null) {
                        // Append charset parameter
                        sb.append("; charset=");
                        sb.append(charsetParameter);
                    }
                }

                // Set new content type
                final String overriddenContentType = sb.toString();
                headersMap.put("Content-Type", new String[] { overriddenContentType });

                // Extract action parameter if present
                String actionParameter = null;
                if (parameters != null) {
                    actionParameter = parameters.get("action");
                    if (actionParameter != null) {
                        // Set SOAPAction header
                        headersMap.put("SOAPAction", new String[] { actionParameter });
                    }
                }

                if (indentedLogger.isDebugEnabled()) {
                    indentedLogger.logDebug(LOG_TYPE, "found SOAP POST", "request Content-Type",
                            overriddenContentType, "request SOAPAction header", actionParameter);
                }

                return true;
            }
        } else {
            if (httpMethod.equals("GET") && NetUtils.APPLICATION_SOAP_XML.equals(contentTypeMediaType)) {
                // SOAP GET

                final Map<String, String> parameters = NetUtils.getContentTypeParameters(contentType);
                final StringBuilder sb = new StringBuilder(NetUtils.APPLICATION_SOAP_XML);

                // Extract charset parameter if present
                if (parameters != null) {
                    final String charsetParameter = parameters.get("charset");
                    if (charsetParameter != null) {
                        // Append charset parameter
                        sb.append("; charset=");
                        sb.append(charsetParameter);
                    }
                }

                // Set Accept header with optional charset
                final String acceptHeader = sb.toString();
                headersMap.put("Accept", new String[] { acceptHeader });

                if (indentedLogger.isDebugEnabled()) {
                    indentedLogger.logDebug(LOG_TYPE, "found SOAP GET", "request Accept header", acceptHeader);
                }

                return true;
            }
        }

        return false;
    }

    private boolean isHTTPOrHTTPS(String scheme) {
        return scheme.equals("http") || scheme.equals("https");
    }

    /**
     * Get user-specified list of headers to forward.
     *
     * @param headersToForward  space-separated list of headers to forward
     * @return  Map<String, String> lowercase header name to user-specified header name or null if null String passed
     */
    public static Map<String, String> getHeadersToForward(String headersToForward) {
        if (headersToForward == null)
            return null;

        final Map<String, String> result = new HashMap<String, String>();
        for (final StringTokenizer st = new StringTokenizer(headersToForward, ", "); st.hasMoreTokens();) {
            final String currentHeaderName = st.nextToken().trim();
            final String currentHeaderNameLowercase = currentHeaderName.toLowerCase();
            result.put(currentHeaderNameLowercase, currentHeaderName);
        }
        return result;
    }

    public static void logRequestBody(IndentedLogger indentedLogger, String mediatype, byte[] messageBody)
            throws UnsupportedEncodingException {
        if (XMLUtils.isXMLMediatype(mediatype) || XMLUtils.isTextOrJSONContentType(mediatype)
                || (mediatype != null && mediatype.equals("application/x-www-form-urlencoded"))) {
            indentedLogger.logDebug("submission", "setting request body", "body", new String(messageBody, "UTF-8"));
        } else {
            indentedLogger.logDebug("submission", "setting binary request body");
        }
    }

    private static StateScope getStateScope() {
        // NOTE: Property values are same as enum except in lowercase
        final PropertySet propertySet = org.orbeon.oxf.properties.Properties.instance().getPropertySet();
        final String stateScope = propertySet.getString(HTTP_STATE_PROPERTY,
                DEFAULT_STATE_SCOPE.name().toLowerCase());
        return StateScope.valueOf(stateScope.toUpperCase());
    }

    /**
     * Get the list of headers to forward from the configuration properties
     *
     * @return space-separated list of header names
     */
    public static String getForwardHeaders() {
        final PropertySet propertySet = org.orbeon.oxf.properties.Properties.instance().getPropertySet();
        return propertySet.getString(HTTP_FORWARD_HEADERS_PROPERTY, "");
    }

    public static String[] getForwardCookies() {
        final PropertySet propertySet = org.orbeon.oxf.properties.Properties.instance().getPropertySet();
        final String stringValue = propertySet.getString(HTTP_FORWARD_COOKIES_PROPERTY, "JSESSIONID JSESSIONIDSSO");
        return org.apache.commons.lang.StringUtils.split(stringValue);
    }
}