Package org.objectweb.joram.mom.dest.jms

Source Code of org.objectweb.joram.mom.dest.jms.JMSModule$XARecoverDaemon

/*
* JORAM: Java(TM) Open Reliable Asynchronous Messaging
* Copyright (C) 2010 ScalAgent Distributed Technologies
*
* 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): ScalAgent Distributed Technologies
* Contributor(s):
*/
package org.objectweb.joram.mom.dest.jms;

import java.util.Properties;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import javax.jms.Session;
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.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.common.Daemon;
import fr.dyade.aaa.common.Debug;

public class JMSModule implements ExceptionListener {

  private static final Logger logger = Debug.getLogger(JMSModule.class.getName());

  /** <code>true</code> if the module is fully usable. */
  protected boolean usable = true;

  /** Message explaining why the module is not usable. */
  protected String notUsableMessage;

  /** Daemon used for the reconnection process. */
  protected ReconnectionDaemon reconnectionDaemon;

  /** serializable object for synchronization */
  protected Object lock = new String();

  /** Indicates to use an XAConnection. Default is false. */
  protected boolean isXA = false;

  /** Connection to the foreign JMS server. */
  protected Connection cnx;

  /** Session with the foreign JMS destination. */
  protected Session session;

  /** XAResource */
  protected XAResource xaRes = 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;

  /** 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;

  /** Foreign JMS destination object. */
  protected Destination dest = null;

  /** JMS clientID field. */
  protected String clientID = null;

  /** Connection factory object for connecting to the foreign JMS server. */
  protected ConnectionFactory cnxFact = null;

  public void init(Properties properties) {
    if (logger.isLoggable(BasicLevel.DEBUG)) {
      logger.log(BasicLevel.DEBUG, "<init>(" + properties + ')');
    }

    jndiFactory = properties.getProperty("jndiFactory");
    jndiUrl = properties.getProperty("jndiUrl");

    cnxFactName = properties.getProperty("connectionFactoryName");
    if (cnxFactName == null) {
      throw new IllegalArgumentException("Missing ConnectionFactory JNDI name.");
    }

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

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

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

    clientID = properties.getProperty("clientId");

    isXA = Boolean.valueOf(properties.getProperty("useXAConnection", "false")).booleanValue();

    try {
      connect();
    } catch (JMSException exc) {
      if (logger.isLoggable(BasicLevel.ERROR)) {
        logger.log(BasicLevel.ERROR, "Not usable: ", exc);
      }
    }
  }

  public void close() {
    if (logger.isLoggable(BasicLevel.DEBUG)) {
      logger.log(BasicLevel.DEBUG, "close()");
    }

    if (cnx != null) {
      try {
        cnx.setExceptionListener(null);
      } catch (JMSException exc1) {
        logger.log(BasicLevel.ERROR, "", exc1);
      }
    }

    if (cnx != null) {
      try {
        cnx.stop();
      } catch (JMSException exc) {
      }
    }

    if (reconnectionDaemon != null) {
      try {
        reconnectionDaemon.stop();
      } catch (Exception exc) {
      }
    }

    if (cnx != null) {
      try {
        cnx.close();
      } catch (JMSException exc) {
      }
    }
  }

  /**
   * 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.
   */
  private void connect() throws JMSException {
    if (!usable) {
      throw new IllegalStateException(notUsableMessage);
    }

    if (logger.isLoggable(BasicLevel.DEBUG)) {
      logger.log(BasicLevel.DEBUG, "connect()");
    }

    // Creating the module's daemons.
    reconnectionDaemon = new ReconnectionDaemon();

    StartupDaemon startup = new StartupDaemon();
    startup.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) {
      cnx = cnxFact.createConnection(userName, password);
    } else {
      cnx = cnxFact.createConnection();
    }
    cnx.setExceptionListener(this);

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

    if (logger.isLoggable(BasicLevel.DEBUG)) {
      logger.log(BasicLevel.DEBUG, "doConnect: cnx=" + cnx);
    }

    session = cnx.createSession(false, Session.AUTO_ACKNOWLEDGE);
  }

  /**
   * 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) {
      cnx = ((XAConnectionFactory) cnxFact).createXAConnection(userName, password);
    } else {
      cnx = ((XAConnectionFactory) cnxFact).createXAConnection();
    }
    cnx.setExceptionListener(this);

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

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

    session = ((XAConnection) cnx).createXASession();

    xaRes = ((XASession) session).getXAResource();

    if (logger.isLoggable(BasicLevel.DEBUG)) {
      logger.log(BasicLevel.DEBUG, "doXAConnect: res=" + xaRes);
    }

    // Recover if needed.
    new XARecoverDaemon(xaRes).start();
  }

  /**
   * 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();
  }

  protected void connectionDone() {
    // Nothing to do;
  }

  /**
   * 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("StartupDaemon", logger);
      setDaemon(false);
      if (logmon.isLoggable(BasicLevel.DEBUG)) {
        logmon.log(BasicLevel.DEBUG, "StartupDaemon<init>");
      }
    }

    /** The daemon's loop. */
    public void run() {
      if (logmon.isLoggable(BasicLevel.DEBUG)) {
        logmon.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 (logmon.isLoggable(BasicLevel.DEBUG)) {
            logmon.log(BasicLevel.DEBUG, "run: factory=" + cnxFact + ", destination=" + dest);
          }

        }
        try {
          if (isXA) {
            doXAConnect();
          } else {
            doConnect();
          }
          if (usable) {
            connectionDone();
          }

        } catch (AbstractMethodError exc) {
          usable = false;
          notUsableMessage = "Retrieved administered objects types not "
              + "compatible with the 'unified' communication " + " mode: " + exc;
          if (logmon.isLoggable(BasicLevel.DEBUG)) {
            logmon.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 (logmon.isLoggable(BasicLevel.DEBUG)) {
            logmon.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 (logmon.isLoggable(BasicLevel.DEBUG)) {
            logmon.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
          }
        } catch (JMSException exc) {
          if (logmon.isLoggable(BasicLevel.DEBUG)) {
            logmon.log(BasicLevel.DEBUG, "Exception:: ", exc);
          }
          reconnectionDaemon.reconnect();
        } catch (Throwable exc) {
          usable = false;
          notUsableMessage = "" + exc;
          if (logmon.isLoggable(BasicLevel.DEBUG)) {
            logmon.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 (logmon.isLoggable(BasicLevel.DEBUG)) {
          logmon.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
        }
      } catch (javax.naming.NamingException exc) {
        usable = false;
        notUsableMessage = "Could not access JNDI: " + exc;
        if (logmon.isLoggable(BasicLevel.DEBUG)) {
          logmon.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 (logmon.isLoggable(BasicLevel.DEBUG)) {
          logmon.log(BasicLevel.DEBUG, "Exception:: notUsableMessage=" + notUsableMessage, exc);
        }
      } catch (Exception exc) {
        usable = false;
        notUsableMessage = "Error while retrieving administered objects " + "through JNDI: " + exc;
        if (logmon.isLoggable(BasicLevel.DEBUG)) {
          logmon.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("ReconnectionDaemon", logger);
      setDaemon(false);
      if (logmon.isLoggable(BasicLevel.DEBUG)) {
        logmon.log(BasicLevel.DEBUG, "ReconnectionDaemon<init>");
      }
    }

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

      if (running) {
        return;
      }

      start();
    }

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

      int attempts = 0;
      long interval;

      try {
        while (running) {
          canStop = true;

          attempts++;

          if (attempts <= attempts1) {
            interval = interval1;
          } else if (attempts <= attempts2) {
            interval = interval2;
          } else {
            interval = interval3;
          }

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

          } catch (Exception exc) {
            continue;
          }
          canStop = false;
          break;
        }
      } finally {
        finish();
      }
    }

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

    /** 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("XARecoverDaemon", logger);
      this.resource = resource;
      if (logmon.isLoggable(BasicLevel.DEBUG)) {
        logmon.log(BasicLevel.DEBUG, "XARecoverDaemon<init>");
      }
    }

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

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

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

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

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

        try {
          Xid[] xids = resource.recover(XAResource.TMNOFLAGS);
          if (logmon.isLoggable(BasicLevel.DEBUG)) {
            logmon.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 (logmon.isLoggable(BasicLevel.INFO)) {
              logmon
                  .log(BasicLevel.INFO, "XARecoverDaemon : commit this " + xids[i].getGlobalTransactionId());
            }
            resource.commit(xids[i], false);
            if (logmon.isLoggable(BasicLevel.DEBUG)) {
              logmon.log(BasicLevel.DEBUG, "run: XA commit xid=" + xids[i]);
            }
          }

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

}
TOP

Related Classes of org.objectweb.joram.mom.dest.jms.JMSModule$XARecoverDaemon

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.