
/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 *
 */

package org.jboss.tm;

import java.net.InetAddress;
import javax.transaction.xa.Xid;
import java.net.UnknownHostException;


/**
 * XidFactory.java
 *
 * Created: Sat Jun 15 19:01:18 2002
 *
 * Modified: 10 Oct 2002, 16:44
 * <a href="mailto:jamie_r_burns@hotmail.com">Jamie Burns</a>
 *
 * If we stop/start JBoss but leave MS SQLServer running, MS SQLServer
 * fails when it encounters an Xid with a global transaction id that it
 * has seen before. XidFactory has been changed so that each instance of
 * XidFactory has a unique baseGlobalId each time JBoss starts.
 *
 * baseGlobalId format is now
 *
 *     hostName//instanceId/timeStampId/
 *
 * Each Xid will have a global transaction id in the following format
 *
 *    baseGlobalId/globalIdNumber
 *
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @version
 *
 * @jmx.mbean
 */

public class XidFactory implements XidFactoryMBean
{
    /**
     * Used to prevent multiple instances of XidFactory creating
     * Xid's with colliding global transaction ids.
     */
    private static long instanceCount = 1;


    /**
     * The id of this instance of XidFactory. Value of instanceCount
     * at time of this XidFactory's creation. Used with timestampId and
     * the host name, creates a unique baseGlobalId.
     */
    private String instanceId;


    /**
     * The system time in milliseconds when this XidFactory was
     * created. Used with instanceId and the host name, creates a
     * unique baseGlobalId.
     */
    private String timestampId;


   /**
    *  The default value of baseGlobalId is the host name of this host,
    *  followed by a slash.
    *
    *  This is used for building globally unique transaction identifiers.
    *  It would be safer to use the IP address, but a host name is better
    *  for humans to read and will do for now.
    * This must be set individually if multiple jboss instances are
    * running on the same machine.
    *
    * In the constructor we ensure there is space for a 14 digit serial
    * number. The baseGlobalId setter does not check the value being set
    * leaves room for a 14 digit serial number. How should a value
    * that is longer than Xid.MAXGTRIDSIZE - 14 be dealt with?
    */
   private String baseGlobalId;

   /**
    * The next transaction id to use on this host.
    */
   private long globalIdNumber = 0;

   /**
    * The variable <code>pad</code> says whether the byte[] should be their
    * maximum 64 byte length or the minimum.
    * The max length is required for Oracle..
    *
    */
   private boolean pad = false;

   /**
    * The variable <code>noBranchQualifier</code> is the 0 or 64 byte zero array
    * used for initial xids.
    *
    */
   private byte[] noBranchQualifier = new byte[0];

   public XidFactory()
   {
      long currentInstanceCount;

      synchronized(XidFactory.class) {

          currentInstanceCount = instanceCount++;
      }

      instanceId = Long.toString(currentInstanceCount);
      timestampId = Long.toString(System.currentTimeMillis());

      String hostName;

      try {
         hostName = InetAddress.getLocalHost().getHostName();
      } catch (UnknownHostException e) {
         hostName = "localhost";
      }

      StringBuffer tempBaseGlobalId = new StringBuffer(Xid.MAXGTRIDSIZE);

      tempBaseGlobalId.append("//");
      tempBaseGlobalId.append(instanceId);
      tempBaseGlobalId.append("/");
      tempBaseGlobalId.append(timestampId);
      tempBaseGlobalId.append("/");

      // Ensure room for 14 digits of serial no.

      int spaceForHostName = Xid.MAXGTRIDSIZE - 14 - tempBaseGlobalId.length();

      if(hostName.length() > spaceForHostName)
          tempBaseGlobalId.insert(0, hostName.substring(0, spaceForHostName - 1));
      else
          tempBaseGlobalId.insert(0, hostName);

      baseGlobalId = tempBaseGlobalId.toString();
   }



   /**
    * mbean get-set pair for field BaseGlobalId
    * Get the value of BaseGlobalId
    * @return value of BaseGlobalId
    *
    * @jmx:managed-attribute
    */
   public String getBaseGlobalId()
   {
      return baseGlobalId;
   }


   /**
    * Set the value of BaseGlobalId
	*
	* In the constructor we ensure there is space for a 14 digit serial
    * number. The baseGlobalId setter does not check the value being set
    * leaves room for a 14 digit serial number. How should a value
    * that is longer than Xid.MAXGTRIDSIZE - 14 be dealt with?
	*
    * @param BaseGlobalId  Value to assign to BaseGlobalId
    *
    * @jmx:managed-attribute
    */
   public void setBaseGlobalId(final String baseGlobalId)
   {
      this.baseGlobalId = baseGlobalId;
   }




   /**
    * mbean get-set pair for field globalIdNumber
    * Get the value of globalIdNumber
    * @return value of globalIdNumber
    *
    * @jmx:managed-attribute
    */
   public long getGlobalIdNumber()
   {
      return globalIdNumber;
   }


   /**
    * Set the value of globalIdNumber.
    *
    * There should be a check that baseGlobalId + globalIdNumber does not
    * exceed Xid.MAXGTRIDSIZE.
    *
    * @param globalIdNumber  Value to assign to globalIdNumber
    *
    * @jmx:managed-attribute
    */
   public void setGlobalIdNumber(final long globalIdNumber)
   {
      this.globalIdNumber = globalIdNumber;
   }




   /**
    * mbean get-set pair for field pad
    * Get the value of pad
    * @return value of pad
    *
    * @jmx:managed-attribute
    */
   public boolean isPad()
   {
      return pad;
   }


   /**
    * Set the value of pad
    * @param pad  Value to assign to pad
    *
    * @jmx:managed-attribute
    */
   public void setPad(boolean pad)
   {
      this.pad = pad;
      if (pad)
      {
         noBranchQualifier = new byte[Xid.MAXBQUALSIZE];
      } // end of if ()
      else
      {
         noBranchQualifier = new byte[0];
      } // end of else
   }




   /**
    * mbean get-set pair for field instance
    * Get the value of instance
    * @return value of instance
    *
    * @jmx:managed-attribute
    */
   public XidFactoryMBean getInstance()
   {
      return this;
   }


   /**
    * Describe <code>newXid</code> method here.
    *
    * @return a <code>Xid</code> value
    * @jmx.managed-operation
    */
   public Xid newXid()
   {
      byte[] globalId = (baseGlobalId + Long.toString(getNextId())).getBytes();
      if (pad)
      {
         byte[] result = new byte[Xid.MAXGTRIDSIZE];
         System.arraycopy(globalId, 0, result, 0, globalId.length);
         globalId = result;
      } // end of if ()
      return new XidImpl(globalId, noBranchQualifier);
   }

   /**
    * Describe <code>newBranch</code> method here.
    *
    * @param xid a <code>Xid</code> value
    * @param branchIdNum a <code>long</code> value
    * @return a <code>Xid</code> value
    * @jmx.managed-operation
    */
   public Xid newBranch(Xid xid, long branchIdNum)
   {
      byte[] branchId = Long.toString(branchIdNum).getBytes();
      if (pad)
      {
         byte[] result = new byte[Xid.MAXBQUALSIZE];
         System.arraycopy(branchId, 0, result, 0, branchId.length);
         branchId = result;
      } // end of if ()
      return new XidImpl(xid, branchId);
   }

   /**
    * Describe <code>toString</code> method here.
    *
    * @param xid a <code>Xid</code> value
    * @return a <code>String</code> value
    * @jmx.managed-operation
    */
   public String toString(Xid xid)
   {
      if (xid instanceof XidImpl)
      {
         return XidImpl.toString((XidImpl)xid);
      } // end of if ()
      else
      {
         return xid.toString();
      } // end of else
   }


   private synchronized long getNextId()
   {
      return globalIdNumber++;
   }
}// XidFactory
