Package org.objectweb.joram.mom.dest.jmsbridge

Source Code of org.objectweb.joram.mom.dest.jmsbridge.JMSBridgeModule$StartupDaemon

/*
* JORAM: Java(TM) Open Reliable Asynchronous Messaging
* Copyright (C) 2004 - 2010 ScalAgent Distributed Technologies
* Copyright (C) 2003 - 2004 Bull SA
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
* USA.
*
* Initial developer(s): Frederic Maistre (Bull SA)
* Contributor(s): ScalAgent Distributed Technologies
*/
package org.objectweb.joram.mom.dest.jmsbridge;

import java.util.Properties;
import java.util.Vector;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.IllegalStateException;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import javax.jms.MessageConsumer;
import javax.jms.MessageFormatException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.Topic;
import javax.jms.XAConnection;
import javax.jms.XAConnectionFactory;
import javax.jms.XASession;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.objectweb.joram.client.jms.XidImpl;
import org.objectweb.joram.shared.messages.Message;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.agent.AgentId;
import fr.dyade.aaa.agent.Channel;
import fr.dyade.aaa.agent.Debug;
import fr.dyade.aaa.common.Daemon;

/**
* The <code>BridgeUnifiedModule</code> class is a bridge module based on the
* JMS 1.1 unified semantics and classes.
*/
public class JMSBridgeModule implements javax.jms.ExceptionListener,
                                            javax.jms.MessageListener,
                                            java.io.Serializable {
  /** define serialVersionUID for interoperability */
  private static final long serialVersionUID = 1L;

  /** logger */
  public static Logger logger = Debug.getLogger(JMSBridgeModule.class.getName());
 
  /** Identifier of the agent using this module. */
  protected AgentId agentId;

  /** Name of the JNDI factory class to use. */
  protected String jndiFactory = null;
  /** JNDI URL. */
  protected String jndiUrl = null;
  /** ConnectionFactory JNDI name. */
  protected String cnxFactName;
  /** Destination JNDI name. */
  protected String destName;
  /** Connection factory object for connecting to the foreign JMS server. */
  protected ConnectionFactory cnxFact = null;
  /** Foreign JMS destination object. */
  protected Destination dest = null;
  /** User identification for connecting to the foreign JMS server. */
  protected String userName = null;
  /** User password for connecting to the foreign JMS server. */
  protected String password = null;
  /** JMS clientID field. */
  protected String clientID = null;
  /** Selector for filtering messages. */
  protected String selector;

  /** <code>true</code> if the module is fully usable. */
  protected boolean usable = true;
  /** Message explaining why the module is not usable. */
  protected String notUsableMessage;

  /** Connection to the foreign JMS server. */
  protected transient Connection producerCnx;
  protected transient Connection consumerCnx;
  /** Session for sending messages to the foreign JMS destination. */
  protected transient Session producerSession;
  /** Session for getting messages from the foreign JMS destination. */
  protected transient Session consumerSession;
  /** Producer object. */
  protected transient MessageProducer producer;
  /** Consumer object. */
  protected transient MessageConsumer consumer;

  /** <code>true</code> if a listener has been set on the JMS consumer. */
  protected transient boolean listener;
  /** Vector holding the pending messages to send after reconnection. */
  protected transient Vector qout;

  /** Daemon used for requesting messages. */
  protected transient ConsumerDaemon consumerDaemon;
  /** Daemon used for the reconnection process. */
  protected transient ReconnectionDaemon reconnectionDaemon;

  /**
   * Automatic receive for a bridge Queue.
   * The foreign messages are transfer in bridge queue,
   * without client request.
   */
  private boolean automaticRequest = false;
  /** Indicates to use an XAConnection. Default is false. */
  private boolean isXA = false;
  /** producer XAResource */
  private transient XAResource producerRes = null;
  /** consumer XAResource */
  private transient XAResource consumerRes = null;
  /** serializable object for synchronization */
  private Object lock = new String();

  /**
   * Constructs a <code>BridgeUnifiedModule</code> module.
   *
   * @param prop  JMS properties required for establishing the link with the
   *          foreign JMS server.
   */
  public JMSBridgeModule(Properties prop) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "<init>(" + prop + ')');

    jndiFactory = prop.getProperty("jndiFactory");
    jndiUrl = prop.getProperty("jndiUrl");
   
    cnxFactName = prop.getProperty("connectionFactoryName");
    if (cnxFactName == null)
      throw new IllegalArgumentException("Missing ConnectionFactory JNDI name.");

    destName = prop.getProperty("destinationName");
    if (destName == null)
      throw new IllegalArgumentException("Missing Destination JNDI name.");

    String userName = prop.getProperty("userName");
    String password = prop.getProperty("password");

    if (userName != null && password != null) {
      this.userName = userName;
      this.password = password;
    }

    clientID = prop.getProperty("clientId");
    selector = prop.getProperty("selector");
    automaticRequest = Boolean.valueOf(
          prop.getProperty("automaticRequest","false")).booleanValue();
    isXA = Boolean.valueOf(prop.getProperty("useXAConnection", "false")).booleanValue();
  }


  /**
   * Initializes the module's parameters.
   *
   * @param agentId  Identifier of the agent using the module.
   *
   * @exception IllegalArgumentException  If the provided properties are
   *              invalid.
   */
  public void init(AgentId agentId) {
    this.agentId = agentId;
  }

  /**
   * Launches the connection process to the foreign JMS server.
   *
   * @exception javax.jms.IllegalStateException  If the module can't access
   *              the foreign JMS server.
   * @exception javax.jms.JMSException  If the needed JMS resources can't be
   *              created.
   */
  public void connect() throws JMSException {
    if (! usable)
      throw new IllegalStateException(notUsableMessage);
   
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "connect()");

    listener = false;
    // Creating the module's daemons.
    consumerDaemon = new ConsumerDaemon();
    reconnectionDaemon = new ReconnectionDaemon();

    // start daemon.
    consumerDaemon.start();
   
    // Administered objects have not been retrieved: launching the startup
    // daemon.
    if (cnxFact == null || dest == null) {
      StartupDaemon startup = new StartupDaemon();
      startup.start();
    } else {
      // Administered objects have been retrieved: connecting.
      try {
        if (isXA) {
          doXAConnect();
        } else {
          doConnect();
        }
      } catch (JMSException exc) {
        reconnectionDaemon.reconnect();
      }
    }
  }

  /**
   * Sets a message listener on the foreign JMS destination.
   *
   * @exception javax.jms.IllegalStateException  If the module state does
   *              not allow to set a listener.
   */
  public void setMessageListener() throws IllegalStateException {
    if (! usable)
      throw new IllegalStateException(notUsableMessage);

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "setMessageListener()");
   
    listener = true;
    try {
      setConsumer();
      consumer.setMessageListener(this);
      consumerCnx.start();
    } catch (JMSException exc) {}
  }

  /**
   * Unsets the set message listener on the foreign JMS destination.
   */
  public void unsetMessageListener() {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "unsetMessageListener()");
   
    try {
      consumerCnx.stop();
      consumer.setMessageListener(null);
      unsetConsumer();
    } catch (JMSException exc) {}
    listener = false;
  }

  /**
   * Synchronous method requesting an immediate delivery from the foreign
   * JMS destination.
   *
   * @return  The JMS message formatted into a JORAM MOM message, or
   *          <code>null</code> if no message is available or if the request
   *          fails.
   *
   * @exception javax.jms.IllegalStateException  If the module state does
   *              not allow to request a message.
   */
  public Message receiveNoWait() throws IllegalStateException {
    if (! usable)
      throw new IllegalStateException(notUsableMessage);

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "receiveNoWait()");
   
    Message momMessage = null;
   
    if (! automaticRequest) {
      // Be careful, with automaticRequest set to true this code causes a dead-lock.
      // In this case if there is no available message return null.
     
      synchronized (lock) {
        try {
          setConsumer();
          consumerCnx.start();
          Xid xid = null;
          try {
            if (isXA) {
              xid = new XidImpl(new byte[0], 1, (agentId.toString() + System.currentTimeMillis()).getBytes());
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "receiveNoWait: XA xid=" + xid);

              try {
                consumerRes.start(xid, XAResource.TMNOFLAGS);
              } catch (XAException e) {
                if (logger.isLoggable(BasicLevel.WARN))
                  logger.log(BasicLevel.WARN, "Exception:: XA can't start resource : " + consumerRes, e);
              }
            }
            javax.jms.Message msg = consumer.receiveNoWait();
            if (msg != null) {
              org.objectweb.joram.client.jms.Message clientMessage =
                org.objectweb.joram.client.jms.Message.convertJMSMessage(msg);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "receiveNoWait: clientMessage=" + clientMessage);

              momMessage = clientMessage.getMomMsg();
            } else {
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "receiveNoWait: no message available");
            }

            if (isXA) {
              try {
                consumerRes.end(xid, XAResource.TMSUCCESS);
                if (logger.isLoggable(BasicLevel.DEBUG))
                  logger.log(BasicLevel.DEBUG, "receiveNoWait: XA end " + consumerRes);

              } catch (XAException e) {
                throw new JMSException("XA resource end(...) failed: " + consumerRes + " :: " + e.getMessage());
              }
              try {
                int ret = consumerRes.prepare(xid);
                if (ret == XAResource.XA_OK)
                  consumerRes.commit(xid, false);
                if (logger.isLoggable(BasicLevel.DEBUG))
                  logger.log(BasicLevel.DEBUG, "receiveNoWait: XA commit " + consumerRes);
              } catch (XAException e) {
                try {
                  consumerRes.rollback(xid);
                  if (logger.isLoggable(BasicLevel.DEBUG))
                    logger.log(BasicLevel.DEBUG, "receiveNoWait: XA rollback" + consumerRes);
                } catch (XAException e1) { }
                throw new JMSException("XA resource rollback(" + xid + ") failed: " +
                                       consumerRes + " :: " + e.getMessage());
              }
            } else {
              consumerSession.commit();
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "receiveNoWait: commit " + consumerSession);
            }
          } catch (MessageFormatException exc) {
            // Conversion error: denying the message.
            if (isXA) {
              try {
                consumerRes.rollback(xid);
                if (logger.isLoggable(BasicLevel.DEBUG))
                  logger.log(BasicLevel.DEBUG, "receiveNoWait: XA rollback " + consumerRes);
              } catch (XAException e1) { }
            } else {
              consumerSession.rollback();
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "receiveNoWait: rollback " + consumerSession);
            }
          }
        } catch (JMSException commitExc) {
          // Connection start, or session commit/rollback failed:
          // setting the message to null.
          momMessage = null;
        }
      }
    }
   
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "receiveNoWait: momMessage=" + momMessage);

    return momMessage;
  }

  /**
   * Asynchronous method requesting a delivery from the foreign
   * JMS destination.
   *
   * @exception javax.jms.IllegalStateException  If the module state does
   *              not allow to request a message.
   */
  public void receive() throws IllegalStateException {
    if (! usable)
      throw new IllegalStateException(notUsableMessage);
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "receive()");
   
    consumerDaemon.receive();
  }
 
  /**
   * Sends a message to the foreign JMS destination.
   *
   * @exception javax.jms.IllegalStateException  If the module's state does
   *              not permit message sendings.
   * @exception javax.jms.MessageFormatException  If the MOM message could not
   *              be converted into a foreign JMS message.
   */
  public void send(org.objectweb.joram.shared.messages.Message message)
  throws JMSException {
    if (! usable)
      throw new IllegalStateException(notUsableMessage);

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "send(" + message + ')');

    synchronized (lock) {
      try {
        Xid xid = null;
        if (isXA) {
          xid = new XidImpl(new byte[0], 1, message.id.getBytes());
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "send: xid=" + xid);

          try {
            producerRes.start(xid, XAResource.TMNOFLAGS);
          } catch (XAException e) {
            if (logger.isLoggable(BasicLevel.WARN))
              logger.log(BasicLevel.WARN, "Exception:: XA can't start resource : " + producerRes, e);
          }
        }
        producer.send(org.objectweb.joram.client.jms.Message.wrapMomMessage(null, message));
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "send: " + producer + " send.");
        acknowledge(message);
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "send: acknowledge.");
        if (isXA) {
          try {
            producerRes.end(xid, XAResource.TMSUCCESS);
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "send: XA end " + producerRes);
          } catch (XAException e) {
            throw new JMSException("resource end(...) failed: " + producerRes + " :: " + e.getMessage());
          }
          try {
            int ret = producerRes.prepare(xid);
            if (ret == XAResource.XA_OK)
              producerRes.commit(xid, false);
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "send: XA commit " + producerRes);
          } catch (XAException e) {
            try {
              producerRes.rollback(xid);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "send: XA rollback " + producerRes);
            } catch (XAException e1) { }
            throw new JMSException("XA resource rollback(" + xid + ") failed: " +
                producerRes + " :: " + e.getMessage());
          }
        }
      } catch (javax.jms.JMSException exc) {
        // Connection failure? Keeping the message for later delivery.
        qout.add(message);
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "send: Exception qout=" + qout);
      }
    }
  }

  /**
   * Interrupts the daemons and closes the connection.
   */
  public void close() {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "close()");

    try {
      consumerCnx.setExceptionListener(null);
      producerCnx.setExceptionListener(null);
    } catch (JMSException exc1) {
      logger.log(BasicLevel.ERROR, "", exc1);
    }
   
    try {
      producerCnx.stop();
      consumerCnx.stop();
    } catch (JMSException exc) {}

    unsetMessageListener();

    try {
      consumerDaemon.stop();
    } catch (Exception exc) {}
    try {
      reconnectionDaemon.stop();
    } catch (Exception exc) {}

    try {
      producerCnx.close();
      consumerCnx.close();
    } catch (JMSException exc) {}
  }

  /**
   * Implements the <code>javax.jms.ExceptionListener</code> interface for
   * catching the failures of the connection to the remote JMS server.
   * <p>
   * Reacts by launching a reconnection process.
   */
  public void onException(JMSException exc) {
    if (logger.isLoggable(BasicLevel.WARN))
      logger.log(BasicLevel.WARN, "onException(" + exc + ')');
    reconnectionDaemon.reconnect();
  }

  /**
   * Implements the <code>javax.jms.MessageListener</code> interface for
   * processing the asynchronous deliveries coming from the foreign JMS
   * server.
   */
  public void onMessage(javax.jms.Message jmsMessage) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "onMessage(" + jmsMessage + ')');
    try {
      Xid xid = null;
      synchronized (lock) {
        try {
          if (isXA) {
            xid = new XidImpl(new byte[0], 1, (agentId.toString() + System.currentTimeMillis()).getBytes());
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "onMessage: xid=" + xid);

            try {
              consumerRes.start(xid, XAResource.TMNOFLAGS);
            } catch (XAException e) {
              if (logger.isLoggable(BasicLevel.WARN))
                logger.log(BasicLevel.WARN, "Exception onMessage:: XA can't start resource : " + consumerRes, e);
            }
          }
          org.objectweb.joram.client.jms.Message clientMessage =
            org.objectweb.joram.client.jms.Message.convertJMSMessage(jmsMessage);
          Message momMessage = clientMessage.getMomMsg();
          if (isXA) {
            try {
              consumerRes.end(xid, XAResource.TMSUCCESS);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "onMessage: XA end " + consumerRes);
            } catch (XAException e) {
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "Exception onMessage:: XA resource end(...) failed: " + consumerRes, e);
              throw new JMSException("onMessage: XA resource end(...) failed: " + consumerRes + " :: " + e.getMessage());
            }
            try {
              int ret = consumerRes.prepare(xid);
              if (ret == XAResource.XA_OK)
                consumerRes.commit(xid, false);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "onMessage: XA commit " + consumerRes);
            } catch (XAException e) {
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "Exception onMessage:: XA resource rollback(" + xid + ")", e);
              try {
                consumerRes.rollback(xid);
                if (logger.isLoggable(BasicLevel.DEBUG))
                  logger.log(BasicLevel.DEBUG, "onMessage: XA rollback " + consumerRes);
              } catch (XAException e1) { }
              throw new JMSException("onMessage: XA resource rollback(" + xid + ") failed: " +
                  consumerRes + " :: " + e.getMessage());
            }

          } else {
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "onMessage: commit.");
            consumerSession.commit();
          }
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "onMessage: send JMSBridgeDeliveryNot.");
          Channel.sendTo(agentId, new JMSBridgeDeliveryNot(momMessage));

        } catch (MessageFormatException conversionExc) {
          // Conversion error: denying the message.
          if (isXA) {
            try {
              consumerRes.rollback(xid);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "run: XA rollback " + consumerRes);
            } catch (XAException e1) { }
          } else {
            consumerSession.rollback();
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "Exception:: onMessage: rollback.");
          }
        }
      }
    } catch (JMSException exc) {
      // Commit or rollback failed: nothing to do.
    }
  }

  /**
   * Opens a XA connection with the foreign JMS server and creates the
   * XA JMS resources for interacting with the foreign JMS destination.
   *
   * @exception JMSException  If the needed JMS resources could not be created.
   */
  protected void doXAConnect() throws JMSException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "doXAConnect()");
   
    if (userName != null && password != null) {
      producerCnx = ((XAConnectionFactory) cnxFact).createXAConnection(userName, password);
      consumerCnx = ((XAConnectionFactory) cnxFact).createXAConnection(userName, password);
    } else {
      producerCnx = ((XAConnectionFactory) cnxFact).createXAConnection();
      consumerCnx = ((XAConnectionFactory) cnxFact).createXAConnection();
    }
    producerCnx.setExceptionListener(this);
    consumerCnx.setExceptionListener(this);

    if (clientID != null) {
      producerCnx.setClientID(clientID);
      consumerCnx.setClientID(clientID);
    }

    producerCnx.start();
    consumerCnx.start();
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "doXAConnect: cnx=" + producerCnx + ", consumerCnx=" + consumerCnx);

    producerSession = ((XAConnection) producerCnx).createXASession();
    producer = producerSession.createProducer(dest);
    consumerSession = ((XAConnection) consumerCnx).createXASession();
   
    producerRes = ((XASession) producerSession).getXAResource();
    consumerRes = ((XASession) consumerSession).getXAResource();
   
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "doXAConnect: producerRes=" + producerRes + ", consumerRes=" + consumerRes);
   
    // Recover if needed.
    new XARecoverDaemon(producerRes).start();
    new XARecoverDaemon(consumerRes).start();
  }

  /**
   * Opens a connection with the foreign JMS server and creates the
   * JMS resources for interacting with the foreign JMS destination.
   *
   * @exception JMSException  If the needed JMS resources could not be created.
   */
  protected void doConnect() throws JMSException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "doConnect()");
   
    if (userName != null && password != null) {
      producerCnx = cnxFact.createConnection(userName, password);
      consumerCnx = cnxFact.createConnection(userName, password);
    } else {
      producerCnx = cnxFact.createConnection();
      consumerCnx = cnxFact.createConnection();
    }
    producerCnx.setExceptionListener(this);
    consumerCnx.setExceptionListener(this);

    if (clientID != null) {
      producerCnx.setClientID(clientID);
      consumerCnx.setClientID(clientID);
    }
   
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "doConnect: cnx=" + producerCnx + ", consumerCnx=" + consumerCnx);
   
    producerSession = producerCnx.createSession(false, Session.AUTO_ACKNOWLEDGE);
    producer = producerSession.createProducer(dest);

    consumerSession = consumerCnx.createSession(true, 0);
  }
 
  /**
   * Sets the JMS consumer on the foreign destination.
   *
   * @exception JMSException  If the JMS consumer could not be created.
   */
  protected void setConsumer() throws JMSException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "setConsumer()");
   
    if (consumer != null)
      return;

    try {
      if (dest instanceof Queue)
        consumer = consumerSession.createConsumer(dest, selector);
      else
        consumer = consumerSession.createDurableSubscriber((Topic) dest,
                                                           agentId.toString(),
                                                           selector,
                                                           false);
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "setConsumer: consumer=" + consumer);
     
    } catch (JMSException exc) {
      throw exc;
    } catch (Exception exc) {
      throw new JMSException("JMS resources do not allow to create consumer: "
                             + exc);
    }
  }

  /**
   * Unsets the JMS consumer.
   */
  protected void unsetConsumer() {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "unsetConsumer()");
    try {
      if (dest instanceof Topic)
        consumerSession.unsubscribe(agentId.toString());

      consumer.close();
    }
    catch (Exception exc) {}

    consumer = null;
  }

  /**
   * Acknowledges a message successfully delivered to the foreign JMS server.
   */
  protected void acknowledge(Message message) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "acknowledge(" + message + ')');
    Channel.sendTo(agentId, new JMSBridgeAckNot(message.id));
  }

  /**
   * The <code>StartupDaemon</code> thread is responsible for retrieving
   * the needed JMS administered objects from the JNDI server.
   */
  protected class StartupDaemon extends Daemon {
    /** Constructs a <code>StartupDaemon</code> thread. */
    protected StartupDaemon() {
      super(agentId.toString() + ":StartupDaemon");
      setDaemon(false);
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "StartupDaemon<init> " + agentId);
    }

    /** The daemon's loop. */
    public void run() {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "run()");
     
      javax.naming.Context jndiCtx = null;
      ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
      try {
        canStop = true;

        // Administered objects still to be retrieved: getting them from
        // JNDI.
        if (cnxFact == null || dest == null) {

          Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

          if (jndiFactory == null || jndiUrl == null)
            jndiCtx = new javax.naming.InitialContext();
          else {
            java.util.Hashtable env = new java.util.Hashtable();
            env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, jndiFactory);
            env.put(javax.naming.Context.PROVIDER_URL, jndiUrl);
            jndiCtx = new javax.naming.InitialContext(env);
          }
          cnxFact = (ConnectionFactory) jndiCtx.lookup(cnxFactName);
          dest = (Destination) jndiCtx.lookup(destName);
         
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "run: factory=" + cnxFact + ", destination=" + dest);
         
          if (dest instanceof Topic) {
            automaticRequest = false;
          }
        }
        try {
          if (isXA) {
            doXAConnect();
          } else {
            doConnect();
          }
         
          // start consumer daemon
          consumerDaemon.start();
        } catch (AbstractMethodError exc) {
          usable = false;
          notUsableMessage = "Retrieved administered objects types not "
                           + "compatible with the 'unified' communication "
                           + " mode: " + exc;
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
        } catch (ClassCastException exc) {
          usable = false;
          notUsableMessage = "Retrieved administered objects types not "
                           + "compatible with the chosen communication mode: "
                           + exc;
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
        } catch (JMSSecurityException exc) {
          usable = false;
          notUsableMessage = "Provided user identification does not allow "
                           + "to connect to the foreign JMS server: "
                           + exc;
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
        } catch (JMSException exc) {
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "Exception:: ", exc);
          reconnectionDaemon.reconnect();
        } catch (Throwable exc) {
          usable = false;
          notUsableMessage = "" + exc;
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
        }
      } catch (javax.naming.NameNotFoundException exc) {
        usable = false;
        if (cnxFact == null)
          notUsableMessage = "Could not retrieve ConnectionFactory ["
                             + cnxFactName
                             + "] from JNDI: " + exc;
        else if (dest == null)
          notUsableMessage = "Could not retrieve Destination ["
                             + destName
                             + "] from JNDI: " + exc;
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
      } catch (javax.naming.NamingException exc) {
        usable = false;
        notUsableMessage = "Could not access JNDI: " + exc;
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
      } catch (ClassCastException exc) {
        usable = false;
        notUsableMessage = "Error while retrieving administered objects "
                           + "through JNDI possibly because of missing "
                           + "foreign JMS client libraries in classpath: "
                           + exc;
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
      } catch (Exception exc) {
        usable = false;
        notUsableMessage = "Error while retrieving administered objects "
                           + "through JNDI: "
                           + exc;
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
      } finally {
        Thread.currentThread().setContextClassLoader(oldClassLoader);
        // Closing the JNDI context.
        try {
          jndiCtx.close();
        }
        catch (Exception exc) {}

        finish();
      }
    }

    /** Shuts the daemon down. */
    public void shutdown()
    {}

    /** Releases the daemon's resources. */
    public void close()
    {}
  }

  /**
   * The <code>ReconnectionDaemon</code> thread is responsible for reconnecting
   * the bridge module with the foreign JMS server in case of disconnection.
   */
  protected class ReconnectionDaemon extends Daemon {
//    /** Number of reconnection trials of the first step. */
//    private int attempts1 = 30;
    /** Retry interval (in milliseconds) of the first step. */
    private long interval1 = 1000L;
//    /** Number of reconnection trials of the second step. */
//    private int attempts2 = 55;
    /** Retry interval (in milliseconds) of the second step. */
    private long interval2 = 5000L;
    /** Retry interval (in milliseconds) of the third step. */
    private long interval3 = 60000L;

    /** Constructs a <code>ReconnectionDaemon</code> thread. */
    protected ReconnectionDaemon() {
      super(agentId.toString() + ":ReconnectionDaemon");
      setDaemon(false);
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "ReconnectionDaemon<init> " + agentId);
    }

    /** Notifies the daemon to start reconnecting. */
    protected void reconnect() {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "reconnect() running=" + running);

      if (running)
        return;

      consumer = null;
      start();
    }

    /** The daemon's loop. */
    public void run() {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "run()");
     
      int attempts = 0;
      long interval;

      try {
        while (running) {
          canStop = true;

          attempts++;

          if (attempts <= 30)
            interval = interval1;
          else if (attempts <= 55)        
            interval = interval2;
          else
            interval = interval3;

          try {
            Thread.sleep(interval);
            if (isXA) {
              doXAConnect();
            } else {
              doConnect();
            }

            // Setting the listener, if any.
            if (listener)
              setMessageListener();
            // Starting the consumer daemon:
            consumerDaemon.start();
            // Sending the pending messages, if any:
            while (! qout.isEmpty())
              send((Message) qout.remove(0));
          } catch (Exception exc) {
            continue;
          }
          canStop = false;
          break;
        }
      }
      finally {
        finish();
      }
    }

    /** Shuts the daemon down. */
    public void shutdown()
    {}

    /** Releases the daemon's resources. */
    public void close()
    {}
  }

  /**
   * The <code>ConsumerDaemon</code> thread allows to call
   * <code>MessageConsumer.receive()</code> for requesting a foreign JMS
   * message without blocking the JORAM server.
   */
  protected class ConsumerDaemon extends Daemon {
    /** Counter of pending "receive" requests. */
    private int requests = 0;

    /** object for synchronization */
    private Object consumerLock = new Object();
    /** true if new "receive" request */
    boolean receiveRequest = false;

    /** Constructs a <code>ReceiverDaemon</code> thread. */
    protected ConsumerDaemon() {
      super(agentId.toString() + ":ConsumerDaemon");
      setDaemon(false);
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "ConsumerDaemon<init> " + agentId);
    }

    /** Notifies the daemon of a new "receive" request. */
    protected void receive() {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG,
            "receive() automaticRequest = " + automaticRequest +
            ", receiveRequest = " + receiveRequest);
      if (!automaticRequest) {
        synchronized (consumerLock) {
          requests++;
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "receive(): notify");
          consumerLock.notify();
          receiveRequest = true;
        }
      }
    }

    /**
     * @see fr.dyade.aaa.common.Daemon#start()
     */
    public void start() {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG,
            "start() running =  " + running +
            ", automaticRequest = " + automaticRequest);
     
      if (running)
        return;
     
      super.start();
    }
   
    /** The daemon's loop. */
    public void run() {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "run()");

      try {
        setConsumer();
        consumerCnx.start();
        while (running) {
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "run: receiveRequest=" + receiveRequest +
                ", automaticRequest=" + automaticRequest);
         
          synchronized (consumerLock) {
            if (automaticRequest || receiveRequest) {
              process();
            } else {
              try {
                if (logger.isLoggable(BasicLevel.DEBUG))
                  logger.log(BasicLevel.DEBUG, "run(): wait");
                canStop = true;
                consumerLock.wait();
                canStop = false;
                if (logger.isLoggable(BasicLevel.DEBUG))
                  logger.log(BasicLevel.DEBUG, "run(): after wait");
                if (running)
                  process();
              } catch (InterruptedException e2) {
                break;
              }
            }
            receiveRequest = false;
          }
        }
      } catch (JMSException exc) {
        // Connection loss?
      }
      finally {
        finish();
      }
    }

    private void process() throws JMSException {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "process()");

      Message momMessage;
      JMSBridgeDeliveryNot notif;

      canStop = true;
      Xid xid = null;
      synchronized (lock) {
        try {
          if (isXA) {
            xid = new XidImpl(new byte[0], 1, (agentId.toString() + System.currentTimeMillis()).getBytes());
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "run: xid=" + xid);

            try {
              consumerRes.start(xid, XAResource.TMNOFLAGS);
            } catch (XAException e) {
              if (logger.isLoggable(BasicLevel.WARN))
                logger.log(BasicLevel.WARN,
                    "Exception:: XA can't start resource : " + consumerRes +
                    ", xid = " + xid, e);
            }
          }
          org.objectweb.joram.client.jms.Message clientMessage =
            org.objectweb.joram.client.jms.Message.convertJMSMessage(consumer.receive());

          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "run: clientMessage=" + clientMessage);

          momMessage = clientMessage.getMomMsg();
          if (isXA) {
            try {
              consumerRes.end(xid, XAResource.TMSUCCESS);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "run: XA end " + consumerRes);
            } catch (XAException e) {
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG,
                    "Exception:: XA resource end(...) failed: " + consumerRes +
                    ", xid = " + xid, e);
              throw new JMSException("XA resource end(...) failed: " + consumerRes + " :: " + e.getMessage());
            }
            try {
              int ret = consumerRes.prepare(xid);
              if (ret == XAResource.XA_OK)
                consumerRes.commit(xid, false);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "run: XA commit " + consumerRes);
            } catch (XAException e) {
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "Exception:: XA resource rollback(" + xid + ")", e);
              try {
                consumerRes.rollback(xid);
                if (logger.isLoggable(BasicLevel.DEBUG))
                  logger.log(BasicLevel.DEBUG, "run: XA rollback " + consumerRes);
              } catch (XAException e1) { }
              throw new JMSException("XA resource rollback(" + xid + ") failed: " +
                  consumerRes + " :: " + e.getMessage());
            }
          } else {
            consumerSession.commit();
          }
        } catch (MessageFormatException messageExc) {
          // Conversion error: denying the message.
          if (isXA) {
            try {
              consumerRes.rollback(xid);
              if (logger.isLoggable(BasicLevel.DEBUG))
                logger.log(BasicLevel.DEBUG, "run: XA rollback " + consumerRes);
            } catch (XAException e1) { }
          } else {
            consumerSession.rollback();
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "run: rollback " + consumerSession);
          }
          return;
        }
      }
      // Processing the delivery.
      canStop = false;
      notif = new JMSBridgeDeliveryNot(momMessage);
      Channel.sendTo(agentId, notif);
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "run: sendTo momMessage=" + momMessage);
      if (!automaticRequest)
        requests--;
    }
   
    /** Shuts the daemon down. */
    public void shutdown() {
      consumerLock.notify();
    }

    /** Releases the daemon's resources. */
    public void close() {
    }
  }

  protected class XARecoverDaemon extends Daemon {
    private XAResource resource = null;
   
    /** Constructs a <code>XARecoverDaemon</code> thread. */
    protected XARecoverDaemon(XAResource resource) {
      super(agentId.toString() + ":XARecoverDaemon");
      this.resource = resource;
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "XARecoverDaemon<init> " + agentId);
    }

    /** Releases the daemon's resources. */
    protected void close() {
    }

    /** Shuts the daemon down. */
    protected void shutdown() {
    }

    /** The daemon's loop. */
    public void run() {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "run()");

      synchronized (lock) {
        Xid xid = new XidImpl(new byte[0], 1, (agentId.toString() + System.currentTimeMillis()).getBytes());
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "run: xid = " + xid);

        try {
          resource.start(xid, XAResource.TMNOFLAGS);
        } catch (XAException exc) {
          if(logger.isLoggable(BasicLevel.WARN))
            logger.log(BasicLevel.WARN, "Exception:: XA can't start resource : " + resource, exc);
        }

        try {
          Xid[] xids = resource.recover(XAResource.TMNOFLAGS);
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "run: XA xid.length=" + xids.length);
          // if needed recover this resource, and commit.
          for (int i = 0; i < xids.length; i++) {
            if (logger.isLoggable(BasicLevel.INFO))
              logger.log(BasicLevel.INFO, "XARecoverDaemon : commit this " + xids[i].getGlobalTransactionId());
            resource.commit(xids[i], false);
            if (logger.isLoggable(BasicLevel.DEBUG))
              logger.log(BasicLevel.DEBUG, "run: XA commit xid=" + xids[i]);
          }

          // ended the recover.
          resource.end(xid, XAResource.TMSUCCESS);
        } catch (XAException e) {
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "Exception:: run", e);
        }
      }
    }
  }

  /** Deserializes a <code>BridgeUnifiedModule</code> instance. */
  private void readObject(java.io.ObjectInputStream in)
               throws java.io.IOException, ClassNotFoundException {
    in.defaultReadObject();
    qout = new Vector();
  }
}
TOP

Related Classes of org.objectweb.joram.mom.dest.jmsbridge.JMSBridgeModule$StartupDaemon

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.