Package com.atomikos.jms

Source Code of com.atomikos.jms.MessageConsumerSession$ReceiverThread

/**
* Copyright (C) 2000-2010 Atomikos <info@atomikos.com>
*
* This code ("Atomikos TransactionsEssentials"), by itself,
* is being distributed under the
* Apache License, Version 2.0 ("License"), a copy of which may be found at
* http://www.atomikos.com/licenses/apache-license-2.0.txt .
* You may not use this file except in compliance with the License.
*
* While the License grants certain patent license rights,
* those patent license rights only extend to the use of
* Atomikos TransactionsEssentials by itself.
*
* This code (Atomikos TransactionsEssentials) contains certain interfaces
* in package (namespace) com.atomikos.icatch
* (including com.atomikos.icatch.Participant) which, if implemented, may
* infringe one or more patents held by Atomikos.
* It should be appreciated that you may NOT implement such interfaces;
* licensing to implement these interfaces must be obtained separately from Atomikos.
*
* 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.
*/

package com.atomikos.jms;

import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.Topic;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;

import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.logging.Logger;
import com.atomikos.logging.LoggerFactory;

/**
*
*
* Common message-driven session functionality.
*
*/

public abstract class MessageConsumerSession
{
  private static final Logger LOGGER = LoggerFactory.createLogger(MessageConsumerSession.class);

  private static final int DEFAULT_TIMEOUT = 30;
  private AbstractConnectionFactoryBean factory;
  private String user;
  private String password;
  private Destination destination;
  private int timeout;
  private boolean notifyListenerOnClose;
  private String messageSelector;
  private boolean daemonThreads;
  private transient MessageListener listener;
  protected transient Thread current;
  private UserTransactionManager tm;
  private boolean active;
  private ExceptionListener exceptionListener;

  protected MessageConsumerSession()
  {
    timeout = DEFAULT_TIMEOUT;
    tm = new UserTransactionManager();

  }

  protected abstract String getSubscriberName();
  protected abstract void setSubscriberName ( String name );
  protected abstract void setNoLocal ( boolean value );

  protected abstract boolean getNoLocal();

  protected void setAbstractConnectionFactoryBean ( AbstractConnectionFactoryBean bean )
  {
    this.factory = bean;
  }

  protected AbstractConnectionFactoryBean getAbstractConnectionFactoryBean()
  {
    return factory;
  }

  /**
   * Sets whether threads should be daemon threads or not.
   * Default is false.
   * @param value If true then threads will be daemon threads.
   */
  public void setDaemonThreads ( boolean value )
  {
      this.daemonThreads = value;
  }

  /**
   * Tests whether threads are daemon threads.
   * @return True if threads are deamons.
   */
  public boolean getDaemonThreads()
  {
      return daemonThreads;
  }

  /**
   * Get the message selector (if any)
   *
   * @return The selector, or null if none.
   */
  public String getMessageSelector()
  {
      return messageSelector;
  }

  /**
   * Set the message selector to use.
   *
   * @param selector
   */
  public void setMessageSelector(String selector)
  {
      this.messageSelector = selector;
  }

  /**
   * Set the user to create connections with. If the user is not set then the
   * default connection will be used.
   *
   * @param user
   */
  public void setUser(String user) {
      this.user = user;
  }

  /**
   * Get the user to connect with.
   *
   * @return The user or null if no explicit authentication is to be used.
   */
  public String getUser() {
      return user;
  }

  /**
   * Set the password to use for connecting. This property only needs to be
   * set if the User property was also set.
   *
   * @param password
   */
  public void setPassword(String password) {
      this.password = password;
  }

  /**
   * Gets the destination.
   *
   * @return Null if none was set.
   */
  public Destination getDestination()
  {
    return destination;
  }

  /**
   * Sets the destination to listen on.
   * @param destination
   */
  public void setDestination ( Destination destination )
  {
    this.destination = destination;
  }

  /**
   * Set the transaction timeout, after which long transactions are rolled
   * back automatically. The transaction timeout indirectly determines how
   * long an incoming message can remain in the queue before it is detected by
   * the listener threads. A smaller value means that listener threads will be
   * more actively checking the queues, but this implies a faster invalidation
   * of active transactions due to timeout, and more thread overhead.
   *
   * @param seconds
   *            The timeout for transactions started by the session.
   */
  public void setTransactionTimeout(int seconds) {
      this.timeout = seconds;
  }

  /**
   * Get the transaction timeout in seconds.
   *
   * @return
   */
  public int getTransactionTimeout() {
      return timeout;
  }

  /**
   * Set the message listener for this session. Only one message listener per
   * session is allowed. After this method is called, the listener will
   * receive incoming messages in its onMessage method, in a JTA transaction.
   * By default, the receiver will commit the transaction unless the onMessage
   * method throws a runtime exception (in which case rollback will happen).
   *
   * If no more messages are desired, then this method should be called a
   * second time with a null argument.
   *
   * @param listener
   */
  public void setMessageListener(MessageListener listener) {
      this.listener = listener;
  }

  /**
   * Get the message listener of this session, if any.
   *
   * @return
   */
  public MessageListener getMessageListener() {
      return listener;
  }

  /**
   * Start listening for messages.
   *
   */
  public void startListening() throws JMSException, SystemException {

    if ( active ) throw new IllegalStateException ( "MessageConsumerSession: startListening() called a second time without stopListening() in between" );

      if ( destination == null )
          throw new JMSException ( "Please set the Destination first" );
      if ( factory == null )
          throw new JMSException (
                  "Please set the ConnectionFactory first" );


      tm.setStartupTransactionService ( true );
      tm.init();
      //disable startup to avoid threads re-start the core
      //during shutdown!!! (see ISSUE 10084)
      tm.setStartupTransactionService ( false );
      active = true;
      startNewThread();

      StringBuffer msg = new StringBuffer();
      msg.append ( "MessageConsumerSession configured with [" );
      msg.append ( "user=" ).append( getUser() ).append ( ", " );
      msg.append ( "password=" ).append ( password ).append ( ", " );
      msg.append ( "transactionTimeout=" ).append ( getTransactionTimeout() ).append ( ", " );
      msg.append ( "destination=" ).append( getDestinationName() ).append ( ", " );
      msg.append ( "notifyListenerOnClose= " ).append( getNotifyListenerOnClose() ).append( ", " );
      msg.append ( "messageSelector=" ).append( getMessageSelector() ).append( ", " );
      msg.append ( "daemonThreads=" ).append ( getDaemonThreads() ).append ( ", " );
      msg.append ( "messageListener=" ).append ( getMessageListener() ).append ( ", " );
      msg.append ( "exceptionListener=" ).append ( getExceptionListener() ).append ( ", " );
      msg.append ( "connectionFactory=" ).append ( getAbstractConnectionFactoryBean() );
      msg.append ( "]" );
      if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( msg.toString() );

  }

  protected abstract String getDestinationName();

  protected void startNewThread() {
        if ( active ) {
          current = new ReceiverThread ();
          //FIXED 10082
          current.setDaemon ( daemonThreads );
          current.start ();
          if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( "MessageConsumerSession: started new thread: " + current );
        }
        //if not active: ignore
  }

  private synchronized void notifyExceptionListener ( JMSException e )
  {
    if ( exceptionListener != null ) exceptionListener.onException ( e );
  }

  /**
   * Stop listening for messages. If <b>notifyListenerOnClose</b> is set then
   * calling this method will indirectly lead to the invocation of the
   * listener's onMessage method with a null argument (and without a
   * transaction). This allows receivers to detect shutdown.
   *
   */
  public void stopListening() {

      current = null;
      tm.close();
      active = false;
  }

  /**
   *
   * Check wether the session is configured to notify the listener upon close.
   *
   * @return boolean If true then the listener will receive a null message
   *         when the session is closed.
   *
   */
  public boolean getNotifyListenerOnClose() {
      return notifyListenerOnClose;
  }

  /**
   * Set whether the listener should be notified on close.
   *
   * @param b
   */
  public void setNotifyListenerOnClose(boolean b) {
      notifyListenerOnClose = b;
  }

    class ReceiverThread extends Thread
      {
          private Connection connection;
          private Session session;

          private ReceiverThread ()
          {
          }

          private synchronized MessageConsumer refresh () throws JMSException
          {
              MessageConsumer ret = null;

              if ( user != null ) {
                  connection = factory.createConnection ( user, password );

              } else {
                  connection = factory.createConnection ();
              }
              connection.start ();
              session = connection.createSession ( true, 0 );

              String subscriberName = getSubscriberName();
              if ( subscriberName == null ) ret = session.createConsumer ( destination, getMessageSelector () , getNoLocal() );
              else ret = session.createDurableSubscriber( ( Topic ) destination , subscriberName , getMessageSelector() , getNoLocal() );

              return ret;
          }

          private synchronized void close ()
          {
              if ( session != null )
                  try {
                      session.close ();
                      session = null;
                  } catch ( JMSException e ) {
                      if ( LOGGER.isInfoEnabled() ) LOGGER.logInfo (
                              "MessageConsumerSession: Error closing JMS session",
                              e );
                      if ( LOGGER.isInfoEnabled() ) LOGGER.logInfo ( "MessageConsumerSession: linked exception is " , e.getLinkedException() );
                  }
              if ( connection != null )
                  try {
                      connection.close ();
                      connection = null;
                  } catch ( JMSException e ) {
                    LOGGER
                              .logInfo (
                                      "MessageConsumerSession: Error closing JMS connection",
                                      e );
                      if ( LOGGER.isInfoEnabled() ) LOGGER.logInfo ( "MessageConsumerSession: linked exception is " , e.getLinkedException() );
                  }
          }

          public void run ()
          {
              MessageConsumer receiver = null;

              try {
                  // FIRST set transaction timeout, to trigger
                  // TM startup if needed; otherwise the logging
                  // to Configuration will not work!
                  tm.setTransactionTimeout ( timeout );
              } catch ( SystemException e ) {
                LOGGER
                          .logWarning (
                                  "MessageConsumerSession: Error in JMS thread while setting transaction timeout",
                                  e );
              }

              LOGGER
                      .logInfo ( "MessageConsumerSession: Starting JMS listener thread." );

              while ( Thread.currentThread () == current ) {

                   if ( LOGGER.isDebugEnabled() ) LOGGER.logDebug ( "MessageConsumerSession: JMS listener thread iterating..." );
                  boolean refresh = false;
                  boolean commit = true;
                  try {
                      Message msg = null;

                      if ( receiver == null )
                          receiver = refresh ();

                      tm.setTransactionTimeout ( timeout );

                      if ( tm.getTransaction () != null ) {
                        LOGGER
                                  .logWarning ( "MessageConsumerSession: Detected pending transaction: "
                                          + tm.getTransaction () );
                          // this is fatal and should not happen due to cleanup in
                          // previous iteration
                          // so if it does happen then everything we assumed and
                          // tried is wrong
                          // meaning that we can only exit the thread
                          throw new IllegalStateException (
                                  "Can't reuse listener thread with pending transaction!" );
                      }

                      tm.begin ();
                      // wait for at most half of the tx timeout
                      msg = receiver.receive ( timeout * 1000 / 2 );

                      try {

                          if ( msg != null && listener != null
                                  && Thread.currentThread () == current ) {
                            LOGGER
                                      .logInfo ( "MessageConsumerSession: Consuming message: "
                                              + msg.toString () );
                              listener.onMessage ( msg );
                              LOGGER
                                      .logDebug ( "MessageConsumerSession: Consumed message: "
                                              + msg.toString () );
                          } else {
                              commit = false;
                          }
                      } catch ( Exception e ) {
                          if ( LOGGER.isInfoEnabled() ) LOGGER.logInfo (
                                  "MessageConsumerSession: Error during JMS processing of message "
                                          + msg.toString () + " - rolling back.",
                                  e );

                          // This happens if the listener generated the error.
                          // In that case, don't refresh the connection but rather
                          // only rollback. There is no reason to assume that the
                          // connection is corrupted here.
                          commit = false;
                      }

                  } catch ( JMSException e ) {
                      LOGGER.logWarning (
                              "MessageConsumerSession: Error in JMS thread", e );
                      Exception linkedException = e.getLinkedException();
                      if ( linkedException != null ) {
                        LOGGER.logWarning ( "Linked JMS exception is: " , linkedException );
                      }
                      // refresh connection to avoid corruption of thread state.
                      refresh = true;
                      commit = false;
                      notifyExceptionListener ( e );

                  } catch ( Exception e ) {
                      LOGGER.logWarning (
                              "MessageConsumerSession: Error in JMS thread", e );
                      // Happens if there is an error not generated by the
                      // listener;
                      // refresh connection to avoid corruption of thread state.
                      refresh = true;
                      commit = false;
                      JMSException listenerError = new JMSException ( "Unexpected error - please see Atomikos console file for more info" );
                      notifyExceptionListener ( listenerError );

                  } finally {

                      // Make sure no tx exists for thread, or we can't reuse
                      // the thread for later transactions!
                      try {
                          if ( commit )
                              tm.commit ();
                          else {
                              tm.rollback ();
                          }
                      } catch ( RollbackException e ) {
                          // thread still OK
                        LOGGER
                                  .logDebug (
                                          "MessageConsumerSession: Error in ending transaction",
                                          e );
                      } catch ( HeuristicMixedException e ) {
                          // thread still OK
                        LOGGER
                                  .logDebug (
                                          "MessageConsumerSession: Error in ending transaction",
                                          e );
                      } catch ( HeuristicRollbackException e ) {
                          // thread still OK
                        LOGGER
                                  .logDebug (
                                          "MessageConsumerSession: Error in ending transaction",
                                          e );
                      } catch ( Exception e ) {
                          // fatal since thread tx may still exist
                        LOGGER
                                  .logWarning (
                                          "MessageConsumerSession: Error ending thread tx association",
                                          e );

                          // In this case, we suspend the tx so that it is no
                          // longer
                          // associated with this thread. This allows thread reuse
                          // for
                          // later messages. If suspend fails, then we can only
                          // start
                          // a new thread.
                          try {
                            LOGGER
                                      .logDebug ( "MessageConsumerSession: Suspending any active transaction..." );
                              // try to suspend
                              tm.suspend ();
                          } catch ( SystemException err ) {
                            LOGGER
                                      .logDebug (
                                              "MessageConsumerSession: Error suspending transaction",
                                              err );
                              // start new thread to replace this one, because we
                              // can't risk a pending transaction
                              try {
                                LOGGER
                                          .logDebug ( "MessageConsumerSession: Starting new thread..." );
                                  startNewThread();
                              } catch ( Exception fatal ) {
                                  // happens if queue or factory no longer set
                                  // in this case, we can't do anything else -
                                  // just let the
                                  // current thread exit and log warning that the
                                  // listener has stopped
                                LOGGER
                                          .logWarning (
                                                  "MessageConsumerSession: Error starting new thread - stopping listener",
                                                  e );
                                  // set current to null to make this thread exit,
                                  // since reuse is impossible due to risk
                                  // of pending transaction!
                                  stopListening ();
                              }
                          }

                      }

                      if ( refresh ) {
                          // close resources here and let the actual refresh be
                          // done
                          // by the next iteration
                          receiver = null;
                          close ();
                      }
                  }

              }
              LOGGER
                      .logInfo ( "MessageConsumerSession: JMS listener thread exiting." );
              if ( listener != null && current == null && notifyListenerOnClose ) {
                  // if this session stops listening (no more threads active) then
                  // notify the listener of shutdown by calling with null argument
                  // System.out.println ( "Stopping listener: " + listener );
                  listener.onMessage ( null );
              }

          }



      }

  /**
   * Gets the exception listener (if any).
   * @return Null if no ExceptionListener was set.
   */
  public ExceptionListener getExceptionListener()
  {
    return exceptionListener;
  }

  /**
   * Sets the exception listener. The listener will be
   * notified of connection-level JMS errors.
   * <b>IMPORTANT:</b> exception listeners will NOT be
   * notified of any errors thrown by the MessageListener.
   * Instead, the ExceptionListener mechanism is meant
   * for system-level connectivity errors towards and from
   * the underlying message system.
   *
   * @param exceptionListener
   */
  public void setExceptionListener ( ExceptionListener exceptionListener )
  {
    this.exceptionListener = exceptionListener;
  }
}
TOP

Related Classes of com.atomikos.jms.MessageConsumerSession$ReceiverThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.