//------------------------------------------------------------------------------
// <copyright file="BrokerResponse.java" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Implement BrokerResponse class.
// </summary>
//------------------------------------------------------------------------------

/**
 * Wraps response messages to provide access to data, faults and user data
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 */
package com.microsoft.hpc.scheduler.session;

import java.util.Locale;

import javax.xml.bind.*;
import javax.xml.namespace.QName;
import javax.xml.soap.*;
import javax.xml.ws.soap.*;
import javax.xml.ws.*;

import org.w3c.dom.NodeList;

import com.microsoft.hpc.scheduler.session.internal.Constant;

/**
 * Wraps response messages to provide access to data, faults, and user data
 * 
 * @param <TMessage>    Response message type
 */
//TODO: Consider whether BrokerResponse should expose raw SOAP Message as property (from .NET)
public class BrokerResponse<TMessage>
{
    //private static readonly MethodInfo GetDetailMethodInfoBase = typeof(MessageFault).GetMethod("GetDetail", Type.EmptyTypes);
    //  MessageFault class is not visible to Java 
    
    /**
     * Response message's buffer
     */
    private SOAPMessage messageBuffer;
    
    /**
     * Type to convert the response message to
     */
    Class<TMessage> typedMessageConverter; 
    
    /**
     * Stores the fault collection
     */
    //private Collection<?> faultCollection;
    
    /**
     * Initializes a new instance of the BrokerResponse class
     * @param typedMessageConverter Converts the message to its type
     * @param messageBuffer Response Message's buffer
     */
    BrokerResponse(Class<TMessage> typedMessageConverter, SOAPMessage messageBuffer)
    {
        this.messageBuffer = messageBuffer;
        this.typedMessageConverter = typedMessageConverter;
        this.result = null;
    }
    
    /**
     * Gets response's data or throw exception if its a fault
     * @return Parses the result from the message, if haven't been done so already, and returns the result of the message
     * @throws SOAPFaultException   An exception thrown from a fault in the SOAP Message
     * @throws SOAPException    An exception thrown from SOAP protocol 
     */
    public TMessage result;
    
    public TMessage getResult() throws SOAPFaultException, SOAPException
    {
        if(result == null)
        {
            // Get the SOAP body
            SOAPBody soapBody = null;
        
            soapBody = messageBuffer.getSOAPBody();
        
            // If it isnt a fault
            if(!soapBody.hasFault())
            {
                try
                {
                    JAXBContext jc = JAXBContext.newInstance(typedMessageConverter);
                    Unmarshaller unmarshaller = jc.createUnmarshaller();
                    result = (TMessage)unmarshaller.unmarshal(messageBuffer.getSOAPBody().extractContentAsDocument(), 
                            typedMessageConverter).getValue();
                } catch (Exception e)
                {
                    this.throwIfFaultUnderstood(soapBody, soapBody.getFault(), Utility.getAction(messageBuffer), Utility.getSOAPVersion(messageBuffer));
                }
                return result;
            }
            // If it is a fault
            else
            {
                SOAPFault messageFault = soapBody.getFault();
                String action = Utility.getAction(messageBuffer);
            
                if(messageFault.hasDetail())
                {
                    //FaultDescription?
                    throw new SOAPFaultException(messageFault);
                }
                this.throwIfFaultUnderstood(soapBody, messageFault, action, Utility.getSOAPVersion(messageBuffer));
            }
        }
        return result;
    }
    
    /**
     * Returns user data supplied when sending the response's corresponding request
     * @return  Returns the data supplied with the responses's request in String format
     */
    public String getUserData()
    {
        try {
            SOAPMessage soapMessage = messageBuffer;
            SOAPHeader soapHeader = soapMessage.getSOAPHeader();
            NodeList nodeList = soapHeader.getElementsByTagNameNS(Constant.HpcHeaderNS, 
                        Constant.UserDataHeaderName);
            return nodeList.item(0).getTextContent();
        }
        catch(Exception e)
        {
            // If the user data cannot be deserialized, return null string.
            return null;
        }
    }
    
    /**
     * Converts fault to exception and throws it
     * @param response  Not used
     * @param fault SOAPFault used to create SOAPFaultException
     * @param action Action epr
     * @param version   SOAP Version
     * 
     * @see SOAPFaultException  
     * @see SOAPException   
     * @throws SOAPFaultException if an exception occurred during SOAP process
     * @throws SOAPException if an exception occurred in SOAP format
     */
    private void throwIfFaultUnderstood(SOAPBody response, SOAPFault fault, String action, SOAPVersion version) throws SOAPFaultException, SOAPException
    {        
        SOAPFaultException exception;
        boolean isSenderFault;
        boolean isReceiverFault;
        QName subCode;
        
        if((exception = new SOAPFaultException(fault)) != null) // will ever return null?
        {
            throw exception; //repetitive
        }
        
        if(version == SOAPVersion.SOAP11)
        {
            isSenderFault = true;
            isReceiverFault = true;
            subCode = fault.getFaultCodeAsQName();
        }
        else
        {
            isSenderFault = fault.getFaultCode().contains("Sender");
            isReceiverFault = fault.getFaultCode().contains("Receiver");
            subCode = (QName) fault.getFaultSubcodes().next(); //more?
        }
        
        if ((subCode != null) && (subCode.getNamespaceURI() != null))
        {
            if(isSenderFault)
            {
                if(subCode.getNamespaceURI().equalsIgnoreCase("http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher"))
                {
                    if(subCode.getLocalPart().equalsIgnoreCase("SessionTerminated"))
                    {
                        throw new WebServiceException(fault.getFaultReasonText(Locale.getDefault()));
                    }
                    
                    if(subCode.getLocalPart().equalsIgnoreCase("TransactionAborted"))
                    {
                        throw new ProtocolException(fault.getFaultReasonText(Locale.getDefault()));
                    }
                }
            }
            
            if(isReceiverFault && subCode.getNamespaceURI().equalsIgnoreCase("http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher"))
            {
                if(subCode.getLocalPart().equalsIgnoreCase("InternalServiceFault"))
                {
                    //if(fault.hasDetail())
                    //{
                    //    throw new SOAPFaultException(fault);
                    //}
                    
                    throw new SOAPFaultException(fault);//should work still?
                }
                      
                if(subCode.getLocalPart().equalsIgnoreCase("DeserializationFailed"))
                {
                    throw new ProtocolException(fault.getFaultReasonText(Locale.getDefault()));
                }
            }
        }
        throw new SOAPFaultException(fault); //repetitive
    }


}
