/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2000-2009 Sun Microsystems, Inc. All rights reserved. 
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License ("CDDL") (collectively, the "License").  You may
 * not use this file except in compliance with the License.  You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or mq/legal/LICENSE.txt.  See the License for the specific language
 * governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at mq/legal/LICENSE.txt.  Sun designates
 * this particular file as subject to the "Classpath" exception as provided by
 * Sun in the GPL Version 2 section of the License file that accompanied this
 * code.  If applicable, add the following below the License Header, with the
 * fields enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or  to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright holder. 
 */

/*
 * @(#)MessageConsumer.cpp	1.32 11/09/07
 */ 

#include "MessageConsumer.hpp"
#include "TextMessage.hpp"
#include "../io/PacketType.hpp"
#include "Session.hpp"
#include "../util/UtilityMacros.h"
#include "../util/LogUtils.hpp"
#include "../cshim/iMQCallbackUtils.hpp"

// The NSPR mutex may return a little earlier than when the timeout 
// completely expires.  We use this threshold to evaluate if it's likely
// that the timeout expired.  
static const double TIMEOUT_THRESHOLD_RATIO = 0.50;
static const PRIntervalTime TIMEOUT_THRESHOLD = 100;

/*
 *
 */
MessageConsumer::MessageConsumer(Session * const sessionArg,
                                 Destination * const destinationArg,
                                 const PRBool isDurableArg,
                                 const UTF8String * const durableNameArg,
                                 const UTF8String * const messageSelectorArg,
                                 const PRBool noLocalArg,
                                 MQMessageListenerFunc messageListenerArg,
                                 void *                messageListenerCallbackDataArg)
{
  CHECK_OBJECT_VALIDITY();

  MQError errorCode = MQ_SUCCESS;
  ASSERT( sessionArg != NULL );
  ASSERT( destinationArg != NULL );
  ASSERT( isDurableArg == (durableNameArg != NULL) );

  init();
  NULLCHK( sessionArg );
  NULLCHK( destinationArg );
  if (isDurableArg == PR_TRUE && durableNameArg == NULL) {
    ERRCHK( MQ_CONSUMER_NO_DURABLE_NAME );
  }
  
  // Set member variables to parameters
  this->session = sessionArg;
  this->isDurable = isDurableArg;
  if (durableNameArg != NULL) {
    this->durableName = (UTF8String*)durableNameArg->clone();
  } else {
    this->durableName = NULL;
  }
  if (messageSelectorArg != NULL) {
    this->messageSelector = (UTF8String*)messageSelectorArg->clone();
  } else {
    this->messageSelector = NULL;
  }

  if (destinationArg->getIsQueue() == PR_TRUE) {
    this->noLocal = PR_FALSE;
  } else {
    this->noLocal = noLocalArg;
  }

  this->messageListener = messageListenerArg;
  this->messageListenerCallbackData = messageListenerCallbackDataArg;
  this->receiveMode = this->session->getReceiveMode();
  if (this->messageListener == NULL) {
    ASSERT(this->receiveMode == SESSION_SYNC_RECEIVE);
  } else {
    ASSERT(this->receiveMode == SESSION_ASYNC_RECEIVE);
  }

  // If the destination is temporary, then we should enforce that it
  // was created by this connection.

  if (destinationArg->getIsTemporary()) {
    const UTF8String * destName = NULL;
    char * tempDestPrefix = NULL;
	destName = destinationArg->getName();
    CNDCHK( destName == NULL, MQ_DESTINATION_NO_NAME );
	tempDestPrefix = this->session->getConnection()->getTemporaryDestinationPrefix(destinationArg->getIsQueue());
    CNDCHK( tempDestPrefix == NULL, MQ_OUT_OF_MEMORY );
    if (STRNCMP(destName->getCharStr(), STRLEN(tempDestPrefix), tempDestPrefix) != 0) {
     DELETE_ARR( tempDestPrefix );
     ERRCHK( MQ_TEMPORARY_DESTINATION_NOT_IN_CONNECTION );
    }
    DELETE_ARR( tempDestPrefix );
  }
  this->destination = destinationArg->clone();
  CNDCHK( this->destination == NULL, MQ_OUT_OF_MEMORY );

  if (this->receiveMode == SESSION_SYNC_RECEIVE) { 
    MEMCHK( this->receiveQueue = new ReceiveQueue() );
    this->receiveQueue->setSyncReceiveConsumer(this);
    if (this->session->getIsStopped()) { 
      this->receiveQueue->stop();
    }
  }

  this->isInitialized = PR_TRUE;
  return;
Cleanup:

  // Have connection delete the receive queues through session.
  DELETE( this->receiveQueue );
  DELETE( this->durableName );
  DELETE( this->messageSelector );

  this->isInitialized = PR_FALSE;
  this->initializationError = errorCode;

  HANDLED_DELETE( this->destination ) ;
}


/*
 *
 */
MessageConsumer::~MessageConsumer()
{
  CHECK_OBJECT_VALIDITY();

  this->close();
  DELETE( this->receiveQueue );
  DELETE( this->durableName );
  DELETE( this->messageSelector );
  this->isInitialized = PR_FALSE;

  HANDLED_DELETE( this->destination );
}


/*
 *
 */
void
MessageConsumer::init()
{
  CHECK_OBJECT_VALIDITY();

  this->session       = NULL;
  this->destination   = NULL;
  this->isDurable     = PR_FALSE;
  this->isTopic       = PR_FALSE;
  this->durableName   = NULL;
  this->messageSelector  = NULL;
  this->noLocal       = PR_FALSE;
  this->consumerID    = LL_Zero();
  this->receiveQueue  = NULL;
  this->isInitialized = PR_FALSE;
  this->initializationError = MQ_SUCCESS;
  this->messageArrivedCallback = NULL;
  this->messageArrivedCallbackData = NULL;
  this->registered   = PR_FALSE;
  this->messageListener = NULL;
  this->messageListenerCallbackData = NULL;
  this->receiveMode  = SESSION_SYNC_RECEIVE;
  this->isClosed     = PR_FALSE;
  this->prefetchMaxMsgCount = -1;
  this->prefetchThresholdPercent = 50;
  this->hasLastDeliveredSysMessageID = PR_FALSE;

}

/*
 * This is should only be called by Session::closeConsumer
 * and MessageConsumer::~MessageConsumer.
 */
MQError
MessageConsumer::close()
{
  CHECK_OBJECT_VALIDITY();

  this->isClosed = PR_TRUE;

  // Close the receiveQueue
  if (this->receiveMode == SESSION_SYNC_RECEIVE) {
    if ((this->receiveQueue == NULL) || (this->receiveQueue->getIsClosed())) {
    return MQ_SUCCESS;
    }
  
    this->receiveQueue->stop();
    this->receiveQueue->close(PR_TRUE);
  }

  return MQ_SUCCESS;
}

/*
 * This should only be called by Session::stop
 */
void
MessageConsumer::stop()
{
  CHECK_OBJECT_VALIDITY();

  if (this->receiveQueue != NULL) {
    this->receiveQueue->stop();
  }
}

/*
 * This should only be called by Session::start
 */
void
MessageConsumer::start()
{
  CHECK_OBJECT_VALIDITY();

  if (this->receiveQueue != NULL) {
    this->receiveQueue->start();
  }
}

/*
 *
 */
const Destination *
MessageConsumer::getDestination() const
{
  CHECK_OBJECT_VALIDITY();

  return this->destination;
}

/*
 *
 */
PRInt64
MessageConsumer::getConsumerID() const
{
  CHECK_OBJECT_VALIDITY();

  return this->consumerID;
}

/*
 *
 */
void
MessageConsumer::setConsumerID(PRInt64 id)
{
  CHECK_OBJECT_VALIDITY();

  this->consumerID = id;
  registered = PR_TRUE;
}

PRBool
MessageConsumer::isRegistered() const
{
  CHECK_OBJECT_VALIDITY();

  return registered;
}

/*
 *
 */
PRBool
MessageConsumer::getIsTopic() const
{
  CHECK_OBJECT_VALIDITY();

  return this->isTopic;
}

/*
 *
 */
PRBool
MessageConsumer::getIsDurable() const
{
  CHECK_OBJECT_VALIDITY();

  return this->isDurable;
}

/*
 *
 */
PRBool
MessageConsumer::getNoLocal() const
{
  CHECK_OBJECT_VALIDITY();

  return this->noLocal;
}

/*
 *
 */
const UTF8String *
MessageConsumer::getDurableName() const
{
  CHECK_OBJECT_VALIDITY();

  return this->durableName;
}

const UTF8String *
MessageConsumer::getMessageSelector() const
{
  CHECK_OBJECT_VALIDITY();

  return this->messageSelector;
}

/*
 *
 */
PRInt32
MessageConsumer::getAckMode() const
{
  CHECK_OBJECT_VALIDITY();

  return this->session->getAckMode();
}

/*
 *
 */
ReceiveMode
MessageConsumer::getReceiveMode() const
{
  CHECK_OBJECT_VALIDITY();

  return this->receiveMode;
}


ReceiveQueue * 
MessageConsumer::getReceiveQueue() const
{
  CHECK_OBJECT_VALIDITY();

  return this->receiveQueue;
}


/*
 *
 */
PRInt32
MessageConsumer::getPrefetchMaxMsgCount() const
{
  CHECK_OBJECT_VALIDITY();

  return this->prefetchMaxMsgCount;
}

/*
 *
 */
void
MessageConsumer::setPrefetchMaxMsgCount(PRInt32 prefetchSize)
{
  CHECK_OBJECT_VALIDITY();

  this->prefetchMaxMsgCount = prefetchSize;
}


/*
 *
 */
PRFloat64
MessageConsumer::getPrefetchThresholdPercent() const
{
  CHECK_OBJECT_VALIDITY();

  return this->prefetchThresholdPercent;
}

/*
 *
 */
void
MessageConsumer::setPrefetchThresholdPercent(PRFloat64 prefetchThreshold)
{
  CHECK_OBJECT_VALIDITY();

  this->prefetchThresholdPercent = prefetchThreshold;
}


/*
 *
 */
PRBool
MessageConsumer::getIsInitialized() const
{
  CHECK_OBJECT_VALIDITY();

  return isInitialized;
}

/*
 *
 */
MQError
MessageConsumer::getInitializationError() const
{
  CHECK_OBJECT_VALIDITY();
  RETURN_IF_ERROR( HandledObject::getInitializationError() );

  return initializationError;
}

/*
 *
 */
Session *
MessageConsumer::getSession() const
{
  CHECK_OBJECT_VALIDITY();

  return this->session;
}

PRBool
MessageConsumer::getHasLastDeliveredSysMessageID() const
{
  return hasLastDeliveredSysMessageID;
}

const SysMessageID *
MessageConsumer::getLastDeliveredSysMessageID() const
{
  return &(this->lastDeliveredSysMessageID);
}

/*
 *
 */
MQError
MessageConsumer::receiveNoWait(Message ** const message)
{
  return this->receive(message, PR_INTERVAL_NO_WAIT);
}

/*
 *
 */
MQError
MessageConsumer::receive(Message ** const message)
{
  return this->receive(message, PR_INTERVAL_NO_TIMEOUT);
}

/*
 *
 */
MQError
MessageConsumer::receive(Message ** const message, 
                         const PRUint32 timeoutMilliSeconds)
{
  CHECK_OBJECT_VALIDITY();

  MQError errorCode = MQ_SUCCESS;
  Packet * packet = NULL;

  NULLCHK( message );
  *message = NULL;

  CNDCHK( !this->getIsInitialized(), MQ_CONSUMER_NOT_INITIALIZED );

  CNDCHK( this->receiveMode != SESSION_SYNC_RECEIVE, MQ_NOT_SYNC_RECEIVE_MODE );

  // Check if the receiveQueue or Session have been closed 
  ASSERT( receiveQueue != NULL );
  CNDCHK( this->isClosed == PR_TRUE, MQ_CONSUMER_CLOSED );
  ASSERT( session != NULL );
  //CNDCHK( session->getIsClosed(), MQ_SESSION_CLOSED );

  if (timeoutMilliSeconds == PR_INTERVAL_NO_WAIT ||
      timeoutMilliSeconds == PR_INTERVAL_NO_TIMEOUT) {
  packet = (Packet*)receiveQueue->dequeueWait(timeoutMilliSeconds);
  } else {
  packet = (Packet*)receiveQueue->dequeueWait(timeoutMilliSeconds * 1000);
  }

  // If no packet was returned, the timeout expired, or the connection was closed
  if (packet == NULL) {
    if (timeoutMilliSeconds == PR_INTERVAL_NO_WAIT) {
      ERRCHK( MQ_NO_MESSAGE );
    } else if (this->receiveQueue->getIsClosed() == PR_TRUE) {
      ERRCHK( MQ_CONSUMER_CLOSED );
    } else if (timeoutMilliSeconds != PR_INTERVAL_NO_TIMEOUT) {
      ERRCHK( MQ_TIMEOUT_EXPIRED );
    } else {
      LOG_WARNING(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID,
                    MQ_CONSUMER_EXCEPTION,
                    "Failed to receive a message." ));

      LOG_FINE(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID,
                 MQ_CONSUMER_EXCEPTION,
                 "MessageConsumer::receive() failing.  "
                 "timeoutMilliSeconds = %d", timeoutMilliSeconds ));

      ERRCHK( MQ_CONSUMER_EXCEPTION );
    }
  }


  // Convert the packet to a Message
  MEMCHK( *message = Message::createMessage(packet) );
  packet = NULL;  // message owns it now

  // Make sure the message was properly initialized.
  ERRCHK( (*message)->getInitializationError() );

  LOG_FINEST(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, MQ_SUCCESS,
               "MessageConsumer::receive allocated new message 0x%p",
               *message ));

  if (this->hasLastDeliveredSysMessageID == PR_FALSE) {
    this->hasLastDeliveredSysMessageID = PR_TRUE;
  }
  this->lastDeliveredSysMessageID = *((*message)->getSystemMessageID()); 

  (*message)->setSession(this->session);
  ERRCHK( session->acknowledge(*message, PR_FALSE) );

  this->session->messageDelivered();

  // The receive has finished
  receiveQueue->receiveDone();

  return MQ_SUCCESS;

Cleanup:
  if (packet != NULL) {
    this->session->messageDelivered();
  }

  DELETE( packet );
  HANDLED_DELETE( *message );

  // The receive has finished
  if (receiveQueue) {
    receiveQueue->receiveDone();
  }

  return errorCode;
}


MQError
MessageConsumer::beforeMessageListener(const Message * message, MQError mqerror)
{
  CHECK_OBJECT_VALIDITY();

  MQError errorCode = MQ_SUCCESS;
  PRBool invoked = PR_FALSE;
  int xaerr = TM_OK;

  ASSERT( this->session->getIsXA() == PR_TRUE );
  XASession * xasession = (XASession *)this->session;
  errorCode = invokeMessageListenerBA(this, xasession->getBeforeMessageListenerFunc(),
                    xasession->getMessageListenerBACallbackData(), message, mqerror, &invoked);
  if (errorCode != MQ_SUCCESS) {
    LOG_WARNING(( CODELOC, XA_SWITCH_LOG_MASK, xasession->getConnection()->id(), errorCode,
    "Invocation of beforeMessageListener callback failed because '%s' (%d) for message 0x%p to consumer 0x%p",
                errorStr(errorCode), errorCode, message, this ));
    return errorCode;
  }
  return errorCode;
}


void
MessageConsumer::afterMessageListener(const Message * message, MQError mqerror)
{
  CHECK_OBJECT_VALIDITY();

  MQError errorCode = MQ_SUCCESS;
  PRBool invoked = PR_FALSE;

  ASSERT( this->session->getIsXA() == PR_TRUE );
  XASession * xasession = (XASession *)this->session;
  errorCode = invokeMessageListenerBA(this, xasession->getAfterMessageListenerFunc(),
                xasession->getMessageListenerBACallbackData(), message, mqerror, &invoked);
  if (errorCode != MQ_SUCCESS) {
    LOG_WARNING(( CODELOC, XA_SWITCH_LOG_MASK, xasession->getConnection()->id(), errorCode,
    "Invocation of afterMessageListener callback failed because '%s' (%d) for message 0x%p to consumer 0x%p",
                errorStr(errorCode), errorCode, message, this ));
  }
  return;
}


MQError
MessageConsumer::onMessage(Message * message, PRBool * messageListenerInvoked)
{
  CHECK_OBJECT_VALIDITY();

  MQError errorCode = MQ_SUCCESS;
  MQError ackErrorCode = MQ_SUCCESS;
  *messageListenerInvoked = PR_FALSE;

  if (this->isClosed == PR_TRUE) {
    return MQ_CONSUMER_CLOSED;
  }
  ASSERT( this->session != NULL );
  if (this->session->getIsXA() == PR_TRUE) { 
    if ((errorCode = beforeMessageListener(message, MQ_SUCCESS)) != MQ_SUCCESS) {
      return errorCode;
    }
  }
  message->setSession(this->session);
  errorCode = invokeMessageListener(this, this->messageListener, 
                                    this->messageListenerCallbackData, 
                                     message, messageListenerInvoked);
  if (*messageListenerInvoked != PR_TRUE) {
    LOG_FINE(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, errorCode,
               "Attempt to invoke message listener failed because '%s' (%d) for message 0x%p to consumer 0x%p",
                errorStr(errorCode), errorCode, message, this ));
    if (this->session->getIsXA() == PR_TRUE) { 
      afterMessageListener(message, errorCode);
    }
    return errorCode;
  }

  if (this->hasLastDeliveredSysMessageID == PR_FALSE) {
    this->hasLastDeliveredSysMessageID = PR_TRUE;
  }
  this->lastDeliveredSysMessageID = *(message->getSystemMessageID());

  if (errorCode == MQ_SUCCESS) {
    LOG_FINEST(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, MQ_SUCCESS,
                 "Successfully delivered message 0x%p to consumer 0x%p message listener",
                  message, this ));
    ERRCHK( this->session->acknowledge(message, PR_TRUE) );
    if (this->session->getIsXA() == PR_TRUE) { 
      afterMessageListener(message, MQ_SUCCESS);
    }
    return MQ_SUCCESS;
  } 

  LOG_WARNING(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, errorCode,
                "Message listener invocation failed because '%s' (%d) for message 0x%p to consumer 0x%p",
                errorStr(errorCode), errorCode, message, this ));
  if (this->session->getAckMode() == AUTO_ACKNOWLEDGE
      || this->session->getAckMode() == DUPS_OK_ACKNOWLEDGE) {
    ERRCHK( message->setJMSRedelivered(PR_TRUE) );
    errorCode = invokeMessageListener(this, this->messageListener, 
                                      this->messageListenerCallbackData,
                                      message, messageListenerInvoked);
    if (*messageListenerInvoked == PR_TRUE && errorCode == MQ_SUCCESS) {
      LOG_FINEST(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, MQ_SUCCESS,
                   "Successfully redelivered message 0x%p to consumer 0x%p message listener",
                   message, this ));
      ERRCHK( this->session->acknowledge(message, PR_TRUE) );
    }
    return errorCode;
  }

  /**
   * now errorCode must be messageListener's return error if not MQ_SUCCESS 
   */
  ackErrorCode = this->session->acknowledge(message, PR_TRUE);
  if (ackErrorCode != MQ_SUCCESS) {
    LOG_SEVERE(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, ackErrorCode,
                 "Async message delivery acknowledgement failed: '%s' (%d) for message 0x%p to consumer 0x%p (MQMessageListenerFunc return=%d)",
                 errorStr(ackErrorCode), ackErrorCode, message, this, errorCode ));
    errorCode = ackErrorCode;
   
  } else if (errorCode != MQ_SUCCESS && errorCode != MQ_CALLBACK_RUNTIME_ERROR) {
    errorCode = MQ_CALLBACK_RUNTIME_ERROR;
  }

Cleanup:
  LOG_WARNING(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, errorCode,
               "Async message delivery processing failed: '%s' (%d) for message 0x%p to consumer 0x%p",
                errorStr(errorCode), errorCode, message, this ));
  if (this->session->getIsXA() == PR_TRUE) { 
    afterMessageListener(message, errorCode);
  }
  return errorCode;
}


/*
 *
 */
HandledObjectType
MessageConsumer::getObjectType() const
{
  CHECK_OBJECT_VALIDITY();

  return MESSAGE_CONSUMER_OBJECT;
}


/*
 *
 */
MQError
MessageConsumer::setMessageArrivedCallback(const MQMessageArrivedFunc messageArrivedFunc,
                                           void * messageArrivedFuncData)
{
  CHECK_OBJECT_VALIDITY();

  if (this->receiveMode != SESSION_SYNC_RECEIVE) return MQ_NOT_SYNC_RECEIVE_MODE;

  this->messageArrivedCallback     = messageArrivedFunc;
  this->messageArrivedCallbackData = messageArrivedFuncData;
  return MQ_SUCCESS;
}


/*
 *
 */
void
MessageConsumer::messageEnqueued() const
{
  CHECK_OBJECT_VALIDITY();

  // If the user has installed a callback for message arrival notfication, 
  // then make the callback.
  if (messageArrivedCallback != NULL) {
    MQError errorCode = invokeMessageArrivedCallback(this, messageArrivedCallback, 
                                                 messageArrivedCallbackData);
    if (errorCode != MQ_SUCCESS) {
      LOG_WARNING(( CODELOC, CONSUMER_LOG_MASK, NULL_CONN_ID, errorCode,
          "Invoking message arrived callback failed because '%s' (%d)",
                                         errorStr(errorCode), errorCode ));
    }
  }
}
