/*
 *  Copyright (c) 2012, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  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.apache.synapse.message.senders.blocking;

import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.addressing.AddressingConstants;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HTTPTransportUtils;
import org.apache.axis2.util.JavaUtils;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.core.axis2.SOAPUtils;
import org.apache.synapse.endpoints.EndpointDefinition;
import org.apache.synapse.rest.RESTConstants;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import org.apache.synapse.util.MessageHelper;

public class BlockingMsgSenderUtils {

    /**
     * Fill client options extracting properties from the original message context and endpoint
     * definition
     *
     * @param endpoint endpoint definition
     * @param clientOptions target client options
     * @param synapseInMsgCtx original message context
     */
    public static void fillClientOptions(EndpointDefinition endpoint, Options clientOptions,
                                         MessageContext synapseInMsgCtx) {

        org.apache.axis2.context.MessageContext axisInMsgCtx
                = ((Axis2MessageContext) synapseInMsgCtx).getAxis2MessageContext();

        if (endpoint != null) {

            // if security is enabled
            boolean wsSecurityEnabled = endpoint.isSecurityOn();
            if (wsSecurityEnabled) {
                String wsSecPolicyKey = endpoint.getWsSecPolicyKey();
                if (wsSecPolicyKey != null) {
                    if (endpoint.isDynamicPolicy()) {
                        wsSecPolicyKey = endpoint.evaluateDynamicEndpointSecurityPolicy(synapseInMsgCtx);
                    }
                    clientOptions.setProperty(
                            SynapseConstants.RAMPART_POLICY,
                            MessageHelper.getPolicy(synapseInMsgCtx, wsSecPolicyKey));
                } else {
                    String inboundWsSecPolicyKey = endpoint.getInboundWsSecPolicyKey();
                    String outboundWsSecPolicyKey = endpoint.getOutboundWsSecPolicyKey();
                    if (inboundWsSecPolicyKey != null) {
                        clientOptions.setProperty(SynapseConstants.RAMPART_IN_POLICY,
                                                  MessageHelper.getPolicy(
                                                          synapseInMsgCtx, inboundWsSecPolicyKey));
                    }
                    if (outboundWsSecPolicyKey != null) {
                        clientOptions.setProperty(SynapseConstants.RAMPART_OUT_POLICY,
                                                  MessageHelper.getPolicy(
                                                          synapseInMsgCtx, outboundWsSecPolicyKey));
                    }
                }
            }

            clientOptions.setUseSeparateListener(endpoint.isUseSeparateListener());
        }

        if (axisInMsgCtx.getSoapAction() != null) {
            clientOptions.setAction(axisInMsgCtx.getSoapAction());
        }

        clientOptions.setExceptionToBeThrownOnSOAPFault(
                "true".equals(synapseInMsgCtx.getProperty(SynapseConstants.FORCE_ERROR_PROPERTY)));

        if (axisInMsgCtx.getProperty(SynapseConstants.DISABLE_CHUNKING) != null &&
            JavaUtils.isTrueExplicitly(axisInMsgCtx.getProperty(SynapseConstants.DISABLE_CHUNKING))) {
            clientOptions.setProperty(HTTPConstants.CHUNKED, "false");
        }
    }

    /**
     * Fill the target message context extracting the required properties of the original message
     * context and the endpoint
     *
     * @param endpoint endpoint definition
     * @param axisOutMsgCtx target message axis2 context
     * @param synapseInMsgCtx original synapse message context
     * @throws org.apache.axis2.AxisFault
     */
    public static void fillMessageContext(EndpointDefinition endpoint,
                                          org.apache.axis2.context.MessageContext axisOutMsgCtx,
                                          MessageContext synapseInMsgCtx)
            throws AxisFault {

        org.apache.axis2.context.MessageContext axisInMsgCtx
                = ((Axis2MessageContext) synapseInMsgCtx).getAxis2MessageContext();

        // Copy properties
        setProperties(axisInMsgCtx, axisOutMsgCtx);

        // Copy Transport headers
        axisOutMsgCtx.setProperty(
                org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS,
                axisInMsgCtx.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS));

        // Endpoint format
        if (endpoint.getFormat() != null) {
            String format = endpoint.getFormat();
            if (SynapseConstants.FORMAT_POX.equals(format)) {
                axisOutMsgCtx.setDoingREST(true);
                axisOutMsgCtx.setProperty(
                        Constants.Configuration.MESSAGE_TYPE,
                        org.apache.axis2.transport.http.HTTPConstants.MEDIA_TYPE_APPLICATION_XML);
                axisOutMsgCtx.setProperty(
                        Constants.Configuration.CONTENT_TYPE,
                        org.apache.axis2.transport.http.HTTPConstants.MEDIA_TYPE_APPLICATION_XML);

            } else if (SynapseConstants.FORMAT_GET.equals(format)) {
                axisOutMsgCtx.setDoingREST(true);
                axisOutMsgCtx.setProperty(Constants.Configuration.HTTP_METHOD,
                                          Constants.Configuration.HTTP_METHOD_GET);
                axisOutMsgCtx.setProperty(Constants.Configuration.MESSAGE_TYPE,
                                          org.apache.axis2.transport.http.HTTPConstants.MEDIA_TYPE_X_WWW_FORM);

            } else if (SynapseConstants.FORMAT_SOAP11.equals(format)) {
                axisOutMsgCtx.setDoingREST(false);
                axisOutMsgCtx.removeProperty(Constants.Configuration.MESSAGE_TYPE);
                // We need to set this explicitly here in case the request was not a POST
                axisOutMsgCtx.setProperty(Constants.Configuration.HTTP_METHOD,
                                          Constants.Configuration.HTTP_METHOD_POST);
                if (axisOutMsgCtx.getSoapAction() == null && axisOutMsgCtx.getWSAAction() != null) {
                    axisOutMsgCtx.setSoapAction(axisOutMsgCtx.getWSAAction());
                }
                if (!axisOutMsgCtx.isSOAP11()) {
                    SOAPUtils.convertSOAP12toSOAP11(axisOutMsgCtx);
                }
            } else if (SynapseConstants.FORMAT_SOAP12.equals(format)) {
                axisOutMsgCtx.setDoingREST(false);
                axisOutMsgCtx.removeProperty(Constants.Configuration.MESSAGE_TYPE);
                // We need to set this explicitly here in case the request was not a POST
                axisOutMsgCtx.setProperty(Constants.Configuration.HTTP_METHOD,
                                          Constants.Configuration.HTTP_METHOD_POST);
                if (axisOutMsgCtx.getSoapAction() == null && axisOutMsgCtx.getWSAAction() != null) {
                    axisOutMsgCtx.setSoapAction(axisOutMsgCtx.getWSAAction());
                }
                if (axisOutMsgCtx.isSOAP11()) {
                    SOAPUtils.convertSOAP11toSOAP12(axisOutMsgCtx);
                }
            } else if (SynapseConstants.FORMAT_REST.equals(format)) {
                if (axisInMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD) != null) {
                    if (axisInMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD).
                            toString().equals(Constants.Configuration.HTTP_METHOD_GET)
                        || axisInMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD).
                            toString().equals(Constants.Configuration.HTTP_METHOD_DELETE)) {
                        axisOutMsgCtx.removeProperty(Constants.Configuration.MESSAGE_TYPE);
                    }
                }
                axisOutMsgCtx.setDoingREST(true);
            }
        }

        // MTOM/SWA
        if (endpoint.isUseMTOM()) {
            axisOutMsgCtx.setDoingMTOM(true);
            // fix / workaround for AXIS2-1798
            axisOutMsgCtx.setProperty(
                    Constants.Configuration.ENABLE_MTOM,
                    Constants.VALUE_TRUE);
            axisOutMsgCtx.setDoingMTOM(true);

        } else if (endpoint.isUseSwa()) {
            axisOutMsgCtx.setDoingSwA(true);
            // fix / workaround for AXIS2-1798
            axisOutMsgCtx.setProperty(
                    Constants.Configuration.ENABLE_SWA,
                    Constants.VALUE_TRUE);
            axisOutMsgCtx.setDoingSwA(true);
        }

        if (endpoint.getCharSetEncoding() != null) {
            axisOutMsgCtx.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING,
                                      endpoint.getCharSetEncoding());
        }

        // HTTP Endpoint : use the specified HTTP method TODO: Remove this after refactoring Http Endpoint logic
        if (endpoint.isHTTPEndpoint()) {
            axisOutMsgCtx.setProperty(
                    Constants.Configuration.HTTP_METHOD,
                    synapseInMsgCtx.getProperty(Constants.Configuration.HTTP_METHOD));
        }

        boolean isRest = SynapseConstants.FORMAT_REST.equals(endpoint.getFormat()) |
                         axisInMsgCtx.isDoingREST();
        if (!isRest && !endpoint.isForceSOAP11() && !endpoint.isForceSOAP12()) {
            isRest = isRequestRest(axisInMsgCtx);
        }
        String restURLPostfix = (String) axisOutMsgCtx.getProperty(NhttpConstants.REST_URL_POSTFIX);

        if (endpoint.getAddress() != null) {
            String address = endpoint.getAddress(synapseInMsgCtx);
            if (isRest && restURLPostfix != null && !"".equals(restURLPostfix)
                    && (address.startsWith("http://") || address.startsWith("https://"))) {
                address = getEPRWithRestURLPostfix(restURLPostfix, address);
            }
            axisOutMsgCtx.setTo(new EndpointReference(address));
        } else {
            EndpointReference endpointReference = axisOutMsgCtx.getTo();
            if (endpointReference != null) {
                if (isRest && restURLPostfix != null && !"".equals(restURLPostfix)) {
                    String address = endpointReference.getAddress();
                    address = getEPRWithRestURLPostfix(restURLPostfix, address);
                    axisOutMsgCtx.setTo(new EndpointReference(address));
                } else {
                    axisInMsgCtx.setTo(endpointReference);
                }
            }
        }

        if (endpoint.isUseSeparateListener()) {
            axisOutMsgCtx.getOptions().setUseSeparateListener(true);
        }

        // set the SEND_TIMEOUT for transport sender
        if (endpoint.getEffectiveTimeout() > 0) {
            if (!endpoint.isDynamicTimeoutEndpoint()) {
                long effectiveTimeout = endpoint.getEffectiveTimeout();
                axisOutMsgCtx.setProperty(SynapseConstants.SEND_TIMEOUT, effectiveTimeout);
                axisOutMsgCtx.setProperty(HTTPConstants.SO_TIMEOUT, (int) effectiveTimeout);
            } else {
                long dynamicTimeout = endpoint.evaluateDynamicEndpointTimeout(synapseInMsgCtx);
                axisOutMsgCtx.setProperty(SynapseConstants.SEND_TIMEOUT, dynamicTimeout);
                axisOutMsgCtx.setProperty(HTTPConstants.SO_TIMEOUT, (int) dynamicTimeout);
            }
        }

        // Check for preserve WS-Addressing
        String preserveAddressingProperty = (String) synapseInMsgCtx.getProperty(
                SynapseConstants.PRESERVE_WS_ADDRESSING);
        if (preserveAddressingProperty != null && Boolean.parseBoolean(preserveAddressingProperty)) {
            axisOutMsgCtx.setMessageID(axisInMsgCtx.getMessageID());
        } else {
            MessageHelper.removeAddressingHeaders(axisOutMsgCtx);
            axisOutMsgCtx.setProperty(
                                AddressingConstants.DISABLE_ADDRESSING_FOR_OUT_MESSAGES, Boolean.TRUE);
        }

    }

    /**
     * Get the modified EPR with rest url postfix
     * @param restURLPostfix Rest URL postfix
     * @param address original EPR
    */
    private static String getEPRWithRestURLPostfix(String restURLPostfix, String address) {
        String url;
        if (!address.endsWith("/") && !restURLPostfix.startsWith("/") &&
            !restURLPostfix.startsWith("?")) {
            url = address + "/" + restURLPostfix;
        } else if (address.endsWith("/") && restURLPostfix.startsWith("/")) {
            url = address + restURLPostfix.substring(1);
        } else if (address.endsWith("/") && restURLPostfix.startsWith("?")) {
            url = address.substring(0, address.length() - 1) + restURLPostfix;
        } else {
            url = address + restURLPostfix;
        }
        return url;
    }

    /**
     * Whether the original request received by the synapse is REST
     *
     * @param originalInMsgCtx request message
     * @return <code>true</code> if the request was a REST request
     */
    private static boolean isRequestRest(org.apache.axis2.context.MessageContext originalInMsgCtx) {

        boolean isRestRequest =
                originalInMsgCtx.getProperty(NhttpConstants.REST_REQUEST_CONTENT_TYPE) != null;

        if (!isRestRequest) {

            String httpMethod = (String) originalInMsgCtx.getProperty(
                    Constants.Configuration.HTTP_METHOD);

            isRestRequest = Constants.Configuration.HTTP_METHOD_GET.equals(httpMethod)
                            || Constants.Configuration.HTTP_METHOD_DELETE.equals(httpMethod)
                            || Constants.Configuration.HTTP_METHOD_PUT.equals(httpMethod)
                            || RESTConstants.METHOD_OPTIONS.equals(httpMethod)
                            || Constants.Configuration.HTTP_METHOD_HEAD.equals(httpMethod);

            if (!isRestRequest) {

                isRestRequest = Constants.Configuration.HTTP_METHOD_POST.equals(httpMethod)
                                && HTTPTransportUtils.isRESTRequest(
                        String.valueOf(originalInMsgCtx.getProperty(
                                Constants.Configuration.MESSAGE_TYPE)));
            }
        }
        return isRestRequest;
    }

    /**
     * Set message context properties extracting from the original message context
     * @param axisInMsgCtx original message context
     * @param axisOutMsgCtx target message context
     */
    private static void setProperties(org.apache.axis2.context.MessageContext axisInMsgCtx,
                                      org.apache.axis2.context.MessageContext axisOutMsgCtx) {
        for (String propertyName : allowedProperties) {
            Object property = axisInMsgCtx.getProperty(propertyName);
            if (property != null) {
                axisOutMsgCtx.setProperty(propertyName, property);
            }
        }
    }

    private static String[] allowedProperties = {"JSON_OBJECT",
                                                 "JSON_STRING",
                                                 "setCharacterEncoding",
                                                 NhttpConstants.DISTRIBUTED_TRANSACTION,
                                                 NhttpConstants.DISTRIBUTED_TRANSACTION_MANAGER,
                                                 Constants.Configuration.HTTP_METHOD,
                                                 Constants.Configuration.MESSAGE_TYPE,
                                                 Constants.Configuration.CONTENT_TYPE,
                                                 NhttpConstants.REST_URL_POSTFIX};

}
