package org.apache.sandesha2.workers;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.client.async.Callback;
import org.apache.axis2.client.async.AxisCallback;
import org.apache.axis2.util.CallbackReceiver;
import org.apache.axis2.addressing.AddressingConstants;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.context.OperationContextFactory;
import org.apache.axis2.context.ServiceContext;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.OutOnlyAxisOperation;
import org.apache.axis2.engine.AxisEngine;
import org.apache.axis2.engine.MessageReceiver;
import org.apache.axis2.engine.Handler.InvocationResponse;
import org.apache.axis2.transport.RequestResponseTransport;
import org.apache.axis2.transport.TransportUtils;
import org.apache.axis2.transport.RequestResponseTransport.RequestResponseTransportStatus;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.sandesha2.RMMsgContext;
import org.apache.sandesha2.Sandesha2Constants;
import org.apache.sandesha2.SandeshaException;
import org.apache.sandesha2.i18n.SandeshaMessageHelper;
import org.apache.sandesha2.i18n.SandeshaMessageKeys;
import org.apache.sandesha2.policy.SandeshaPolicyBean;
import org.apache.sandesha2.storage.SandeshaStorageException;
import org.apache.sandesha2.storage.StorageManager;
import org.apache.sandesha2.storage.Transaction;
import org.apache.sandesha2.storage.beanmanagers.SenderBeanMgr;
import org.apache.sandesha2.storage.beans.RMDBean;
import org.apache.sandesha2.storage.beans.RMSBean;
import org.apache.sandesha2.storage.beans.SenderBean;
import org.apache.sandesha2.util.AcknowledgementManager;
import org.apache.sandesha2.util.MessageRetransmissionAdjuster;
import org.apache.sandesha2.util.MsgInitializer;
import org.apache.sandesha2.util.RMMsgCreator;
import org.apache.sandesha2.util.SandeshaUtil;
import org.apache.sandesha2.util.SpecSpecificConstants;
import org.apache.sandesha2.util.TerminateManager;
import org.apache.sandesha2.wsrm.*;

public class SenderWorker extends SandeshaWorker implements Runnable {

  private static final Log log = LogFactory.getLog(SenderWorker.class);

	private ConfigurationContext configurationContext = null;
	private SenderBean senderBean = null;
	private RMMsgContext messageToSend = null;
	private String rmVersion = null;

	public SenderWorker (ConfigurationContext configurationContext, SenderBean senderBean, String rmVersion) {
		this.configurationContext = configurationContext;
		this.senderBean = senderBean;
		this.rmVersion = rmVersion; 
	}
	
	public void setMessage(RMMsgContext msg) {
		this.messageToSend = msg;
	}
	
	public void run () {
		
		if (log.isDebugEnabled())
			log.debug("Enter: SenderWorker::run");

		Transaction transaction = null;
		
		try {
			StorageManager storageManager = SandeshaUtil.getSandeshaStorageManager(configurationContext, configurationContext.getAxisConfiguration());
			SenderBeanMgr senderBeanMgr = storageManager.getSenderBeanMgr();

			transaction = storageManager.getTransaction();

			String key = senderBean.getMessageContextRefKey();
			MessageContext msgCtx = null;
			RMMsgContext   rmMsgCtx = null;
			if(messageToSend != null) {
				msgCtx = messageToSend.getMessageContext();
				rmMsgCtx = messageToSend;
			} else {
				msgCtx = storageManager.retrieveMessageContext(key, configurationContext);
      
				if (msgCtx == null) {
					// This sender bean has already been processed
					
					if(transaction != null && transaction.isActive()) transaction.commit();
					transaction = null;

					return;
				}
      
				rmMsgCtx = MsgInitializer.initializeMessage(msgCtx);
			}

			// sender will not send the message if following property is
			// set and not true.
			// But it will set if it is not set (null)

			// This is used to make sure that the mesage get passed the
			// Sandesha2TransportSender.

			String qualifiedForSending = (String) msgCtx.getProperty(Sandesha2Constants.QUALIFIED_FOR_SENDING);
			if (qualifiedForSending != null && !qualifiedForSending.equals(Sandesha2Constants.VALUE_TRUE)) {
				if (log.isDebugEnabled())
					log.debug("Exit: SenderWorker::run, !qualified for sending");
				
				if(transaction != null && transaction.isActive()) {
					transaction.commit();
					transaction = null;
				}
				
				return;
			}

			if (msgCtx == null) {
				if (log.isDebugEnabled())
					log.debug(SandeshaMessageHelper.getMessage(SandeshaMessageKeys.sendHasUnavailableMsgEntry));
				
				if(transaction != null && transaction.isActive()) {
					transaction.commit();
					transaction = null;
				}
				
				return;			
			}

			// operation is the lowest level Sandesha2 should be attached
			ArrayList msgsNotToSend = SandeshaUtil.getPropertyBean(msgCtx.getAxisOperation()).getMsgTypesToDrop();

			if (msgsNotToSend != null && msgsNotToSend.contains(new Integer(rmMsgCtx.getMessageType()))) {
				if (log.isDebugEnabled())
					log.debug("Exit: SenderWorker::run, message type to be dropped " + rmMsgCtx.getMessageType());
				
				if(transaction != null && transaction.isActive()) {
					transaction.commit();
					transaction = null;
				}
				
				return;	
			}

			// If we are sending to the anonymous URI then we _must_ have a transport waiting,
			// or the message can't go anywhere. If there is nothing here then we leave the
			// message in the sender queue, and a MakeConnection (or a retransmitted request)
			// will hopefully pick it up soon.
			Boolean makeConnection = (Boolean) msgCtx.getProperty(Sandesha2Constants.MAKE_CONNECTION_RESPONSE);
			EndpointReference toEPR = msgCtx.getTo();

			MessageContext inMsg = null;
			OperationContext op = msgCtx.getOperationContext();
			
			RequestResponseTransport t = (RequestResponseTransport) msgCtx.getProperty(RequestResponseTransport.TRANSPORT_CONTROL);
			
			if (t==null) {
				if (op != null)
					inMsg = op.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
				if (inMsg != null)
					t = (RequestResponseTransport) inMsg.getProperty(RequestResponseTransport.TRANSPORT_CONTROL);
			}

			// If we are anonymous, and this is not a makeConnection, then we must have a transport waiting
			if((toEPR==null || toEPR.hasAnonymousAddress()) &&
			   (makeConnection == null || !makeConnection.booleanValue()) &&
			   (t != null && !t.getStatus().equals(RequestResponseTransportStatus.WAITING))) {
				
				// Mark this sender bean so that we know that the transport is unavailable, if the
				// bean is still stored.
				SenderBean bean = senderBeanMgr.retrieve(senderBean.getMessageID());
				if(bean != null && bean.isTransportAvailable()) {
					bean.setTransportAvailable(false);
					senderBeanMgr.update(bean);
				}
				
				// Commit the update
				if(transaction != null && transaction.isActive()) transaction.commit();
				transaction = null;
				
				if (log.isDebugEnabled())
					log.debug("Exit: SenderWorker::run, no response transport for anonymous message");
				return;
			}
			
			//if the message belong to the Replay Model, it will be send out only if 


			boolean continueSending = updateMessage(rmMsgCtx,senderBean,storageManager);
			//save changes done @ updateMessage -> MessageRetransmissionAdjuster.adjustRetransmittion
			storageManager.getSenderBeanMgr().update(senderBean);
			

			if (!continueSending) { 
				if (log.isDebugEnabled())
					log.debug("Exit: SenderWorker::run, !continueSending");
				
				if(transaction != null && transaction.isActive()) {
					transaction.commit();
					transaction = null;
				}
				invokeCallBackObject(storageManager,msgCtx ,"Exit: SenderWorker::run, !continueSending");
				return;
			}

			int messageType = senderBean.getMessageType();
			
			if (isAckPiggybackableMsgType(messageType)) {
				// Commit the update
				if(transaction != null && transaction.isActive()) transaction.commit();
				transaction = storageManager.getTransaction();

				// Piggyback ack messages based on the 'To' address of the message
				transaction = AcknowledgementManager.piggybackAcksIfPresent(rmMsgCtx, storageManager, transaction);
			}

			// sending the message
			boolean successfullySent = false;

			// Although not actually sent yet, update the send count to indicate an attempt
			if (senderBean.isReSend()) {
				SenderBean bean2 = senderBeanMgr.retrieve(senderBean.getMessageID());
				if (bean2 != null) {
					bean2.setSentCount(senderBean.getSentCount());
					senderBeanMgr.update(bean2);
				}
			}

			// have to commit the transaction before sending. This may
			// get changed when WS-AT is available.
			if(transaction != null) {
				transaction.commit();
				transaction = null;
			}

			msgCtx.getOptions().setTimeOutInMilliSeconds(1000000);
			
			boolean processResponseForFaults = false ;
			try {
				InvocationResponse response = InvocationResponse.CONTINUE;
				
				SandeshaPolicyBean policy = SandeshaUtil.getPropertyBean(msgCtx.getAxisOperation());
				if(policy.isUseMessageSerialization()) {
					if(msgCtx.isPaused()) {
						if (log.isDebugEnabled())
							log.debug("Resuming a send for message : " + msgCtx.getEnvelope().getHeader());
						msgCtx.setPaused(false);
						msgCtx.setProperty(MessageContext.TRANSPORT_NON_BLOCKING, Boolean.FALSE);
						response = AxisEngine.resumeSend(msgCtx);
					} else {
						if (log.isDebugEnabled())
							log.debug("Sending a message : " + msgCtx.getEnvelope().getHeader());
						msgCtx.setProperty(MessageContext.TRANSPORT_NON_BLOCKING, Boolean.FALSE);
						AxisEngine.send(msgCtx);  // TODO check if this should return an invocation response
					}
				} else {
					// had to fully build the SOAP envelope to support
					// retransmissions.
					// Otherwise a 'parserAlreadyAccessed' exception could
					// get thrown in retransmissions.
					// But this has a performance reduction.
					msgCtx.getEnvelope().build();
	
					ArrayList retransmittablePhases = (ArrayList) msgCtx.getProperty(Sandesha2Constants.RETRANSMITTABLE_PHASES);
					if (retransmittablePhases!=null) {
						msgCtx.setExecutionChain(retransmittablePhases);
					} else {
						ArrayList emptyExecutionChain = new ArrayList ();
						msgCtx.setExecutionChain(emptyExecutionChain);
					}
					
					msgCtx.setCurrentHandlerIndex(0);
					msgCtx.setCurrentPhaseIndex(0);
					msgCtx.setPaused(false);
				
					if (log.isDebugEnabled())
						log.debug("Resuming a send for message : " + msgCtx.getEnvelope().getHeader());
					response = AxisEngine.resumeSend(msgCtx);
				}
				if(log.isDebugEnabled()) log.debug("Engine resume returned " + response);
				if(response != InvocationResponse.SUSPEND) {
					if(t != null) {
						if(log.isDebugEnabled()) log.debug("Signalling transport in " + t);
						t.signalResponseReady();
					}
				}
				
				successfullySent = true;
				
			} catch (AxisFault e) {
				//this is a possible SOAP 1.2 Fault. So letting it proceed.
				
				processResponseForFaults = true;
				
				recordError(e, rmMsgCtx, storageManager);
				
			} catch (Exception e) {
				String message = SandeshaMessageHelper.getMessage(
						SandeshaMessageKeys.sendMsgError, e.toString());
				
				if (log.isErrorEnabled())
				  log.error(message, e);
				
				recordError(e, rmMsgCtx, storageManager);
				
			}
			// Establish the transaction for post-send processing
			transaction = storageManager.getTransaction();

			// update or delete only if the object is still present.
			SenderBean bean1 = senderBeanMgr
					.retrieve(senderBean.getMessageID());
			if (bean1 != null) {
				if (senderBean.isReSend()) {
					bean1.setTimeToSend(senderBean.getTimeToSend());
					senderBeanMgr.update(bean1);
				}
			}

			// Commit the transaction to release the SenderBean

			if (transaction!=null)
				transaction.commit();
			
			transaction = null;

            if ((processResponseForFaults || successfullySent) && !msgCtx.isServerSide()) {
                boolean  validCs = checkForSyncResponses(msgCtx );
                if (!validCs) {
                    invokeCallBackObject(storageManager,msgCtx ,
                            "Sandesha2 sender thread has not received a valid CreateSequnceResponse");
                }
            }

            if ((rmMsgCtx.getMessageType() == Sandesha2Constants.MessageTypes.TERMINATE_SEQ)
					&&
					 (Sandesha2Constants.SPEC_2005_02.NS_URI.equals(rmMsgCtx.getRMNamespaceValue()))) {
				try {
					transaction = storageManager.getTransaction();
					//terminate message sent using the SandeshaClient. Since the terminate message will simply get the
					//InFlow of the reference message get called which could be zero sized (OutOnly operations).
					
					// terminate sending side if this is the WSRM 1.0 spec. 
					// If the WSRM versoion is 1.1 termination will happen in the terminate sequence response message.
					
					TerminateSequence terminateSequence = (TerminateSequence) rmMsgCtx
							.getMessagePart(Sandesha2Constants.MessageParts.TERMINATE_SEQ);
					String sequenceID = terminateSequence.getIdentifier().getIdentifier();
	
					RMSBean rmsBean = SandeshaUtil.getRMSBeanFromSequenceId(storageManager, sequenceID);
					TerminateManager.terminateSendingSide(rmsBean, storageManager);
					
					if(transaction != null && transaction.isActive()) transaction.commit();
					transaction = null;
				} finally {
					if(transaction != null && transaction.isActive()) {
						transaction.rollback();
						transaction = null;
					}
				}
			}

		} catch (Exception e) {
			if (log.isDebugEnabled()) log.debug("Caught exception", e);
		} finally {
			if (lock!=null && workId!=null) {
				lock.removeWork(workId);
			}

			if (transaction!=null && transaction.isActive()) {
				try {
					transaction.rollback();
				} catch (SandeshaStorageException e) {
					if (log.isWarnEnabled())
						log.warn("Caught exception rolling back transaction", e);
				}
			}
		}
		
		if (log.isDebugEnabled())
			log.debug("Exit: SenderWorker::run");
	}
	
	/**
	 * Update the message before sending it. We adjust the retransmission intervals and send counts
	 * for the message. If the message is an application message then we ensure that we have added
	 * the Sequence header.
	 */
	private boolean updateMessage(RMMsgContext rmMsgContext, SenderBean senderBean, StorageManager storageManager) throws AxisFault {
		
		// Lock the message to enable retransmission update
		// No need to do the following. This is being done inside run(), which is the callee of
		// updateMessage()
		//senderBean = storageManager.getSenderBeanMgr().retrieve(senderBean.getMessageID());
		
		// Only continue if we find a SenderBean
		if (senderBean == null)
			return false;

		int messageType = senderBean.getMessageType();

		boolean continueSending = MessageRetransmissionAdjuster.adjustRetransmittion(
				rmMsgContext, senderBean, rmMsgContext.getConfigurationContext(), storageManager);
		if(!continueSending) return false;
		
		Identifier id = null;

		if(messageType == Sandesha2Constants.MessageTypes.APPLICATION ||
		   messageType == Sandesha2Constants.MessageTypes.LAST_MESSAGE) {
			
			String namespace = SpecSpecificConstants.getRMNamespaceValue(rmVersion);
			Sequence sequence = (Sequence) rmMsgContext.getMessagePart(Sandesha2Constants.MessageParts.SEQUENCE);
			if(sequence == null) {
				sequence = new Sequence(namespace);
				
				MessageNumber msgNumber = new MessageNumber(namespace);
				msgNumber.setMessageNumber(senderBean.getMessageNumber());
				sequence.setMessageNumber(msgNumber);

				if(senderBean.isLastMessage() &&
				    SpecSpecificConstants.isLastMessageIndicatorRequired(rmVersion)) {
					sequence.setLastMessage(new LastMessage(namespace));
				}
				
				// We just create the id here, we will add the value in later
				id = new Identifier(namespace);
				sequence.setIdentifier(id);
				
				rmMsgContext.setMessagePart(Sandesha2Constants.MessageParts.SEQUENCE, sequence);
				
			}
			
		} else if(messageType == Sandesha2Constants.MessageTypes.TERMINATE_SEQ) {
			TerminateSequence terminate = (TerminateSequence) rmMsgContext.getMessagePart(Sandesha2Constants.MessageParts.TERMINATE_SEQ);
			id = terminate.getIdentifier();

		} else if(messageType == Sandesha2Constants.MessageTypes.CLOSE_SEQUENCE) {
			CloseSequence close = (CloseSequence) rmMsgContext.getMessagePart(Sandesha2Constants.MessageParts.CLOSE_SEQUENCE);
			id = close.getIdentifier();
		
		} else if(messageType == Sandesha2Constants.MessageTypes.ACK_REQUEST) {
			// The only time that we can have a message of this type is when we are sending a
			// stand-alone ack request, and in that case we only expect to find a single ack
			// request header in the message.
			Iterator ackRequests = rmMsgContext.getMessageParts(Sandesha2Constants.MessageParts.ACK_REQUEST);
			AckRequested ackRequest = (AckRequested) ackRequests.next(); 
			if (ackRequests.hasNext()) {
				throw new SandeshaException (SandeshaMessageHelper.getMessage(SandeshaMessageKeys.ackRequestMultipleParts));
			}
			id = ackRequest.getIdentifier();
		}
		
		// TODO consider adding an extra ack request, as we are about to send the message and we
		// know which sequence it is associated with.

		if(id != null && !senderBean.getSequenceID().equals(id.getIdentifier())) {
			id.setIndentifer(senderBean.getSequenceID());

			// Write the changes back into the message context
			rmMsgContext.addSOAPEnvelope();
		}
		
		//if this is an sync WSRM 1.0 case we always have to add an ack
		boolean ackPresent = false;
		Iterator it = rmMsgContext.getMessageParts (Sandesha2Constants.MessageParts.SEQ_ACKNOWLEDGEMENT);
		if (it.hasNext()) 
			ackPresent = true;
		
		if (!ackPresent && rmMsgContext.getMessageContext().isServerSide() 
				&&
			(messageType==Sandesha2Constants.MessageTypes.APPLICATION || 
		     messageType==Sandesha2Constants.MessageTypes.APPLICATION ||
		     messageType==Sandesha2Constants.MessageTypes.UNKNOWN ||
		     messageType==Sandesha2Constants.MessageTypes.LAST_MESSAGE)) {
			
			String inboundSequenceId = senderBean.getInboundSequenceId();
			if (inboundSequenceId==null)
				throw new SandeshaException ("InboundSequenceID is not set for the sequence:" + id);
			
			RMDBean incomingSequenceBean = SandeshaUtil.getRMDBeanFromSequenceId(storageManager, inboundSequenceId);
			
			if (incomingSequenceBean!=null) 
				RMMsgCreator.addAckMessage(rmMsgContext, inboundSequenceId, incomingSequenceBean);
			
		}
		
		return true;
	}
	
	private boolean isAckPiggybackableMsgType(int messageType) {
		if (log.isDebugEnabled())
			log.debug("Enter: SenderWorker::isAckPiggybackableMsgType, " + messageType);
		boolean piggybackable = true;

		if (messageType == Sandesha2Constants.MessageTypes.ACK)
			piggybackable = false;

		if (log.isDebugEnabled())
			log.debug("Exit: SenderWorker::isAckPiggybackableMsgType, " + piggybackable);
		return piggybackable;
	}

    /**
     * return value will be false if the create sequence fails else it will be true
     * @param msgCtx
     * @return
     */
    private boolean checkForSyncResponses(MessageContext msgCtx ) {
		if (log.isDebugEnabled())
			log.debug("Enter: SenderWorker::checkForSyncResponses, " + msgCtx.getEnvelope().getHeader());

		try {

			// create the responseMessageContext

			MessageContext responseMessageContext = msgCtx.getOperationContext().getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
			SOAPEnvelope resenvelope = null;
			if (responseMessageContext!=null)
				resenvelope = responseMessageContext.getEnvelope();
			
			boolean transportInPresent = (msgCtx.getProperty(MessageContext.TRANSPORT_IN) != null);
			if (!transportInPresent && (responseMessageContext==null || responseMessageContext.getEnvelope()==null)) {
				if(log.isDebugEnabled()) log.debug("Exit: SenderWorker::checkForSyncResponses, no response present");
				return true;
			}
			
			//to find out weather the response was built by me.
			boolean syncResponseBuilt = false;
			
			if (responseMessageContext==null || responseMessageContext.getEnvelope()==null) {
				if (responseMessageContext==null)
					responseMessageContext = new MessageContext();

				OperationContext requestMsgOpCtx = msgCtx.getOperationContext();
                responseMessageContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, requestMsgOpCtx
								.getProperty(Constants.Configuration.CHARACTER_SET_ENCODING));
				responseMessageContext.setProperty(Constants.Configuration.CONTENT_TYPE, requestMsgOpCtx
								.getProperty(Constants.Configuration.CONTENT_TYPE));
				responseMessageContext.setProperty(HTTPConstants.MTOM_RECEIVED_CONTENT_TYPE, requestMsgOpCtx
								.getProperty(HTTPConstants.MTOM_RECEIVED_CONTENT_TYPE));
//                responseMessageContext.setOperationContext(requestMsgOpCtx);
//                if ( !requestMsgOpCtx.isComplete()) {
//                    requestMsgOpCtx.addMessageContext(responseMessageContext);
//                }
                responseMessageContext.setAxisOperation(msgCtx.getAxisOperation());

                //If the response MsgCtx was not available Axis2 would hv put the transport info into a
				//HashMap, getting the data from it.
				HashMap transportInfoMap = (HashMap) msgCtx.getProperty(Constants.Configuration.TRANSPORT_INFO_MAP);
				if (transportInfoMap != null) {
					responseMessageContext.setProperty(Constants.Configuration.CONTENT_TYPE, 
									transportInfoMap.get(Constants.Configuration.CONTENT_TYPE));
					responseMessageContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING,
									transportInfoMap.get(Constants.Configuration.CHARACTER_SET_ENCODING));
				}
			
				responseMessageContext.setConfigurationContext(msgCtx.getConfigurationContext());
				responseMessageContext.setTransportIn(msgCtx.getTransportIn());
				responseMessageContext.setTransportOut(msgCtx.getTransportOut());
				responseMessageContext.setProperty(MessageContext.TRANSPORT_IN, msgCtx
						.getProperty(MessageContext.TRANSPORT_IN));
				
				//we will not be setting the operation context here since this msgs may not be an application reply.
				//we let other dispatchers find it.
				responseMessageContext.setServiceContext(msgCtx.getServiceContext());
				responseMessageContext.setServiceGroupContext(msgCtx.getServiceGroupContext());

				responseMessageContext.setProperty(Sandesha2Constants.MessageContextProperties.MAKECONNECTION_ENTRY,
				msgCtx.getProperty(Sandesha2Constants.MessageContextProperties.MAKECONNECTION_ENTRY));

				// If request is REST we assume the responseMessageContext is REST,
				// so set the variable

				responseMessageContext.setDoingREST(msgCtx.isDoingREST());

				resenvelope = responseMessageContext.getEnvelope();
				try {
					// MessageContext is modified in TransportUtils.createSOAPMessage(). It might be used by axis.engine or handler.
					// To catch the modification and pass it to engine or handler, resenvelope is created by responseMessageContext. 
					if (resenvelope==null) {
						//We try to build the response out of the transport stream.
						resenvelope = TransportUtils.createSOAPMessage(responseMessageContext);
						responseMessageContext.setEnvelope(resenvelope);
						syncResponseBuilt = true;
					}
				} catch (AxisFault e) {
					//Cannot find a valid SOAP envelope.
					if (log.isErrorEnabled() ) {
						log.error (SandeshaMessageHelper
								.getMessage(SandeshaMessageKeys.soapEnvNotSet));
					log.error ("Caught exception", e);
					}
				
					return true;
				}
				
				//If addressing is disabled we will be adding this message simply as the application response of the request message.
				Boolean addressingDisabled = (Boolean) msgCtx.getProperty(AddressingConstants.DISABLE_ADDRESSING_FOR_OUT_MESSAGES);
				if (addressingDisabled!=null && Boolean.TRUE.equals(addressingDisabled)) {
					// If the AxisOperation object doesn't have a message receiver, it means that this was
					// an out only op where we have added an ACK to the response.  Set the requestMsgOpCtx to
					// be the RMIn
					OperationContext responseMsgOpCtx = requestMsgOpCtx;
					if (requestMsgOpCtx.getAxisOperation().getMessageReceiver() == null) {
						// Generate a new RM In Only operation

						ServiceContext serviceCtx = responseMessageContext.getServiceContext();
						AxisOperation op = msgCtx.getAxisService().getOperation(Sandesha2Constants.RM_IN_ONLY_OPERATION);
						responseMsgOpCtx = OperationContextFactory.createOperationContext (op.getAxisSpecificMEPConstant(), op, serviceCtx);					

					}
					
					responseMessageContext.setOperationContext(responseMsgOpCtx);
				}
				
				AxisOperation operation = msgCtx.getAxisOperation();
				if (operation!=null && responseMessageContext.getAxisMessage()==null
						&& !(operation instanceof OutOnlyAxisOperation))
					responseMessageContext.setAxisMessage(operation.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE));

				RMMsgContext responseRMMessage = MsgInitializer.initializeMessage(responseMessageContext);
				if (responseRMMessage.getMessageType()==Sandesha2Constants.MessageTypes.ACK) {
					responseMessageContext.setAxisOperation(SpecSpecificConstants.getWSRMOperation
							(Sandesha2Constants.MessageTypes.ACK, responseRMMessage.getRMSpecVersion(), responseMessageContext.getAxisService()));
					responseMessageContext.setOperationContext(null);
				}
				
			}
			
			//if the syncResponseWas not built here and the client was not expecting a sync response. We will not try to execute 
			//here. Doing so will cause a double invocation for a async message. 
			if (msgCtx.getOptions().isUseSeparateListener()==true &&  !syncResponseBuilt) {
				return true;
			}
			
			
			//setting the message as serverSide will let it go through the MessageReceiver (may be callback MR).
			responseMessageContext.setServerSide(true);
		
			if (responseMessageContext.getSoapAction()==null) {
				//if there is no SOAP action in the response message, Axis2 will wrongly identify it as a REST message
				//This happens because we set serverSide to True in a previous step.
				//So we have to add a empty SOAPAction here.
				responseMessageContext.setSoapAction("");
			}

			if (resenvelope!=null) {
				AxisEngine.receive(responseMessageContext);
			}

		} catch (Exception e) {

            String message = SandeshaMessageHelper.getMessage(SandeshaMessageKeys.noValidSyncResponse);
            if (msgCtx != null &&! msgCtx.isServerSide() &&
                    (Sandesha2Constants.SPEC_2005_02.Actions.ACTION_CREATE_SEQUENCE.equals(msgCtx.getSoapAction())
                            || Sandesha2Constants.SPEC_2007_02.Actions.ACTION_CREATE_SEQUENCE.equals(msgCtx.getSoapAction())) ){
                // We have not received a valid createSequnce reponse for the request we send so we need to terminate the seunce here
                return false;
            } else {
                if (log.isWarnEnabled())
                    log.warn(message, e);
            }
		}
		if (log.isDebugEnabled())
			log.debug("Exit: SenderWorker::checkForSyncResponses");
        return true;
    }
	
	private void recordError (Exception e, RMMsgContext outRMMsg, StorageManager storageManager) throws SandeshaStorageException {
		// Store the Exception as a sequence property to enable the client to lookup the last 
		// exception time and timestamp.
		
		Transaction transaction = null;
		
		try
		{
			// Get the internal sequence id from the context
			String internalSequenceId = (String)outRMMsg.getProperty(Sandesha2Constants.MessageContextProperties.INTERNAL_SEQUENCE_ID);
			if(internalSequenceId == null) internalSequenceId = senderBean.getInternalSequenceID();
			
			if(internalSequenceId != null) {
				// Create a new Transaction
				transaction = storageManager.getTransaction();
				
				RMSBean bean = SandeshaUtil.getRMSBeanFromInternalSequenceId(storageManager, internalSequenceId);
			
				if (bean != null) {						
					bean.setLastSendError(e);
					bean.setLastSendErrorTimestamp(System.currentTimeMillis());

					// Update the RMSBean
					storageManager.getRMSBeanMgr().update(bean);
				}
				
				// Commit the properties
				if(transaction != null) {
					transaction.commit();
					transaction = null;
				}
			}
		}
		catch (Exception e1)
		{
			if (log.isErrorEnabled())
				log.error(e1);
		} finally {
			if (transaction != null) {
				transaction.rollback();
				transaction = null;
			}
		}
	}

    private void invokeCallBackObject(StorageManager storageManager,
                                      MessageContext msgCtx,
                                      String message) throws SandeshaStorageException {
        Transaction transaction = null;
        if (msgCtx.isServerSide()) {
            return;
        }
        try {
            transaction = storageManager.getTransaction();
            //terminate message sent using the SandeshaClient. Since the terminate message will simply get the
            //InFlow of the reference message get called which could be zero sized (OutOnly operations).

            // terminate sending side if this is the WSRM 1.0 spec.
            // If the WSRM versoion is 1.1 termination will happen in the terminate sequence response message.

            String internalSequenceId = (String) msgCtx.getProperty(Sandesha2Constants.MessageContextProperties.INTERNAL_SEQUENCE_ID);
            if (internalSequenceId == null) internalSequenceId = senderBean.getInternalSequenceID();
            if (internalSequenceId != null) {
                // Create a new Transaction
                transaction = storageManager.getTransaction();
                RMSBean bean = SandeshaUtil.getRMSBeanFromInternalSequenceId(storageManager, internalSequenceId);
                TerminateManager.terminateSendingSide(bean, storageManager);

                OperationContext opCtx =
                        configurationContext.getOperationContext(bean.getApplicationMessageMessageId());
                if (opCtx != null) {
                    AxisOperation applicationAxisOperation = opCtx.getAxisOperation();
                    if (applicationAxisOperation != null) {
                        MessageReceiver msgReceiver = applicationAxisOperation.getMessageReceiver();
                        if ((msgReceiver != null) && (msgReceiver instanceof CallbackReceiver)) {
                            Object callback = ((CallbackReceiver) msgReceiver)
                                    .lookupCallback(bean.getApplicationMessageMessageId());
                            if (callback != null) {
                                AxisCallback axisCallback = ((AxisCallback) callback);
                                axisCallback.onError(new Exception(message));
                                axisCallback.onComplete();
                            }
                        }
                    }
                }
                if (transaction != null && transaction.isActive()) transaction.commit();
                transaction = null;
            }

        } catch (Exception e) {
            if (log.isWarnEnabled())
                log.warn(e);
        } finally {
            if (transaction != null && transaction.isActive()) {
                transaction.rollback();
                transaction = null;
            }
        }
    }

}
