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

/*
 * %W% %G%
 */ 

package com.sun.messaging.jmq.jmsserver.data.protocol;


import java.io.IOException;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.transaction.xa.XAResource;

import com.sun.messaging.jmq.io.Packet;
import com.sun.messaging.jmq.io.PacketType;
import com.sun.messaging.jmq.io.Status;
import com.sun.messaging.jmq.io.SysMessageID;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.core.Consumer;
import com.sun.messaging.jmq.jmsserver.core.ConsumerUID;
import com.sun.messaging.jmq.jmsserver.core.Destination;
import com.sun.messaging.jmq.jmsserver.core.DestinationUID;
import com.sun.messaging.jmq.jmsserver.core.PacketReference;
import com.sun.messaging.jmq.jmsserver.core.Producer;
import com.sun.messaging.jmq.jmsserver.core.ProducerUID;
import com.sun.messaging.jmq.jmsserver.core.Session;
import com.sun.messaging.jmq.jmsserver.core.SessionUID;
import com.sun.messaging.jmq.jmsserver.data.AutoRollbackType;
import com.sun.messaging.jmq.jmsserver.data.PacketHandler;
import com.sun.messaging.jmq.jmsserver.data.PacketRouter;
import com.sun.messaging.jmq.jmsserver.data.RollbackReason;
import com.sun.messaging.jmq.jmsserver.data.TransactionList;
import com.sun.messaging.jmq.jmsserver.data.TransactionState;
import com.sun.messaging.jmq.jmsserver.data.TransactionUID;
import com.sun.messaging.jmq.jmsserver.data.handlers.AckHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.ClientIDHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.ConsumerHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.DataHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.FlowHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.ProducerHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.QBrowseHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.RedeliverHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.SessionHandler;
import com.sun.messaging.jmq.jmsserver.data.handlers.TransactionHandler;
import com.sun.messaging.jmq.jmsserver.service.imq.IMQConnection;
import com.sun.messaging.jmq.jmsserver.util.BrokerException;
import com.sun.messaging.jmq.util.DestType;
import com.sun.messaging.jmq.util.JMQXid;
import com.sun.messaging.jmq.util.log.Logger;
import com.sun.messaging.jmq.util.selector.Selector;
import com.sun.messaging.jmq.util.selector.SelectorFormatException;



/**
 * 
 * Api used for direct access to the broker. This code is not integrated into 
 * the handler classes because the interaction (e.g. when callbacks happen) 
 * is different for the jms protocol and other protocols.
 */


public class ProtocolImpl implements Protocol
{

    private static boolean DEBUG = false; 

    PacketRouter pr = null; 

    public ProtocolImpl(PacketRouter router)
    {
        pr = router;
    }

    /**
     * called when a new connection is created.
     * <P>Packet:<B>HELLO,HELLO_REPLY</b></p>
     */

    public void hello() {
        // does nothing
    }

    /**
     * Authenticate with the passed in username, password
     * <P>Packet:<B>AUTHENTICATE</b></p>
     */
    public void authenticate(String username, String password)
        throws BrokerException
    {
        /*
         * TBD - currently does nothing
         */
    }


    /**
     * called when a connection is closed
     * <P>Packet:<B>GOODBYE</b></p>
     */
    public void goodbye() {
        // does nothing
    }

    /**
     * gets license information.
     * <P>Packet:<B>GET_LICENSE</b></p>
     *
     *@returns a hashtable with license info
     */

    public Hashtable getLicense() {
        // does nothing
       return new Hashtable();
    }

    /**
     * gets information about the broker/cluster.
     * <P>Packet:<B>INFO_REQUEST</b></p>
     *
     *@param cluster if true, return cluster information otherwise
     *               return local broker information.
     *@returns a hashtable with broker/cluster information
     */

    public Hashtable getInfo(boolean cluster) {
        // currently does nothing
        return new Hashtable();
    }

    /**
     * handles receiving a ping from a client.
     * <P>Packet:<B>PING</b></p>
     */

    public void ping() {
        // does nothing
    }


    /**
     * handles receiving a flow paused from a client.
     * <P>Packet:<B>FLOW_PAUSED</b></p>
     */
    public void flowPaused(int size) {
       throw new UnsupportedOperationException("flow paused is not supported by the client");
    }


    /**
     * Processes acknowledgements.
     * <P>Packet:<B>ACKNOWLEDGE</b></p>
     *
     *@param tid transaction id (or null if no transaction) associated
     *           with the acknowledgement.
     *@param validate  should the acks just be validated (normally false)
     *@param ackType acknowledge type of the request. One of <UL>
     *               <LI>ACKNOWLEDGE == 0 </LI><LI>UNDELIVERABLE==1</LI>
     *               <LI>DEAD</LI></UL>
     *@param exception exception associated with a dead message (should be null
     *                 if ackType != DEAD)
     *@param deadComment the explaination why a message was marked dead (should be null
     *               if ackType != DEAD)
     *@param deliverCnt number of times a dead message was delivered (should be 0
     *               if ackType != DEAD)
     *@param ids  list of message ids to process
     *@param cids list of consumerIDs associated with a message, should directly 
     *            correspond to the same index in ids
     */
    public void acknowledge(IMQConnection con, TransactionUID tid, 
                boolean validate,
                int ackType, Throwable exception, String deadComment,
                int deliverCnt, SysMessageID ids[], ConsumerUID cids[])
           throws BrokerException, IOException
    {
        if (DEBUG) {
        Globals.getLogger().log(Logger.INFO, "ProtocolImpl.acknowledge: ackType"+ackType+", ids="+ids+
        ", cids="+cids+", tid="+tid+", validate="+validate+", deadComment="+deadComment+", deliverCnt="+deliverCnt+", exception="+exception);
        }
        List cleanList = null;
        AckHandler handler = (AckHandler)
                      pr.getHandler(PacketType.ACKNOWLEDGE);
        if (validate) {
            if (ackType == handler.DEAD_REQUEST ||
                ackType == handler.UNDELIVERABLE_REQUEST) {
                throw new BrokerException("Can not use JMQValidate with"
                        + " an ackType of " + ackType,
                        null,
                        Status.BAD_REQUEST);
            } else if (tid == null) {
                throw new BrokerException("Can not use JMQValidate with"
                        + " no tid",
                        null,
                        Status.BAD_REQUEST);
            } else if (!handler.validateMessages(tid, ids, cids)) {
                throw new BrokerException("Acknowledgement could not be found",
                        null,
                        Status.NOT_FOUND);
            }
        } else if (ackType == handler.DEAD_REQUEST) {
            cleanList = handler.handleDeadMsgs(ids, cids, exception, deadComment,
                               deliverCnt);
        } else if (ackType == handler.UNDELIVERABLE_REQUEST) {
            cleanList = handler.handleUndeliverableMsgs(ids, cids);
        
        } else if (tid != null) {
            cleanList = handler.handleTransaction(tid, ids, cids);
        } else {
            cleanList = handler.handleAcks(con, ids, cids, true); //XXX ackack flag
        }

        // cleanup 

        handler.cleanUp(cleanList);
    }

    /**
     *sets/checks the clientID.
     * <P>Packet:<B>SET_CLIENTID</b></p>
     *
     *@param con the connection to set the clientID on
     *@param clientID the clientID to set
     *@param namespace to concatonate to clientID if shared
     *                 (generally set to null)
     *@param share true if the clientID is shared
     *@throws BrokerException if the clientId can not be set
     */

    public void setClientID(IMQConnection con, 
                String clientID, 
                String namespace, 
                boolean share)
        throws BrokerException
    {
        ClientIDHandler handler = (ClientIDHandler)
                      pr.getHandler(PacketType.SET_CLIENTID);
        handler.setClientID(con, clientID, namespace, share);
    }



    /**
     * creates a producer.
     * <P>Packet:<B>ADD_PRODUCER</b></p>
     *
     *@param d the destination to create the producer on
     *@param con the conectio to use for the producer
     *@param uid a unique string used for finding the producer
     *@throws BrokerException if the producer can not be created
     */
    public Producer addProducer(Destination d, IMQConnection con, String uid, boolean acc)
        throws BrokerException
    {
        if (acc)
            checkAccessPermission(PacketType.ADD_PRODUCER, d, con);
        ProducerHandler handler = (ProducerHandler)
                      pr.getHandler(PacketType.ADD_PRODUCER);
        return handler.addProducer(d.getDestinationUID(), con, uid, false);
    }

    /**
     * Destroys a producer.
     * <P>Packet:<B>REMOVE_PRODUCER</b></p>
     *
     *@param d the destination to create the producer on
     *@param con the conectio to use for the producer
     *@param uid a unique string used for finding the producer
     *@throws BrokerException if the producer can not be created
     */
    public void removeProducer(ProducerUID uid, IMQConnection con,
              String suid)
        throws BrokerException
    {
        ProducerHandler handler = (ProducerHandler)
                      pr.getHandler(PacketType.ADD_PRODUCER);
        handler.removeProducer(uid, false, con, suid);
    }

    public void resumeFlow(IMQConnection con, int bufsize)
    {
        FlowHandler handler = (FlowHandler)
                      pr.getHandler(PacketType.RESUME_FLOW);
        handler.connectionFlow(con, bufsize);
    }

    /**
     * resumes flow control on a connection.
     * <P>Packet:<B>RESUME_FLOW</b></p>
     *
     *@param bufsize size of the buffer to receive (-1 indicates unlimited)
     *@param con the consumer to resume
     */
    public void resumeFlow(Consumer con, int bufsize)
    {
        FlowHandler handler = (FlowHandler)
                      pr.getHandler(PacketType.RESUME_FLOW);
        handler.consumerFlow(con, bufsize);
    }

    /**
     * mimics the behavior of DELIVER
     * <P>Packet:<B>DELIVER</b></p>
     *
     *@param cid consumer id to attach to the messages
     *@param ids a list of id's to deliver
     *@return an ArrayList of packets
     */
    public ArrayList deliver(long cid, ArrayList ids)
        throws BrokerException, IOException
    {
        ArrayList returnlist = new ArrayList();
        Iterator itr = ids.iterator();
        while (itr.hasNext()) {
            SysMessageID id = (SysMessageID)itr.next();
            PacketReference ref = Destination.get(id);
            if (ref == null) continue;
            Packet realp = ref.getPacket();
            if (ref.isInvalid()) continue;
            Packet p = new Packet(false /* use direct buffers */);
            p.fill(realp);
            p.setConsumerID(cid);
            returnlist.add(p);
        }
        return returnlist;
    }
    
    /**
     * mimics the behavior of REDELIVER. It retrieves the messages
     * in a similar way but does not retrieve
     * <P>Packet:<B>REDELIVER</b></p>
     *The consumer should stop sessions before requeueing the
     *messages.
     *
     *@param cids consumer id to attach to the messages
     *@param ids a list of id's to deliver
     *@return an ArrayList of packets
     */
    public ArrayList redeliver(TransactionUID tid, ConsumerUID cids[],
            SysMessageID[] ids, boolean redeliver)
        throws BrokerException
    {
        // does nothing currently
        //XXX - TBD
        return new ArrayList();
    }

    

     /**
      * Creates a session and attaches it to the connection
      * <P>Packet:<B>CREATE_SESSION</b></p>
      *@param ackType acknowledge type to use
      *@param con connection to attach the session to
      */
     public Session createSession(int ackType, IMQConnection con)
        throws BrokerException
     {

        Object obj = new Object();
    
        SessionHandler handler = (SessionHandler)
                      pr.getHandler(PacketType.CREATE_SESSION);
    
        return handler.createSession(ackType, obj.toString(), con,
             false);
     }

     /**
      * Destroy a session 
      * <P>Packet:<B>DESTROY_SESSION</b></p>
      *@param uid sessionUID to destroy
      *@param con connection to deattach the session from
      */
     public void destroySession(SessionUID uid, IMQConnection con)
        throws BrokerException
     {
        SessionHandler handler = (SessionHandler)
                      pr.getHandler(PacketType.CREATE_SESSION);
    
        handler.closeSession(uid, con, false);
     }

     /**
      *Pause a session
      *<P>Packet:<B>STOP</b></p>
      *@param uid session to pause
      */
     public void pauseSession(SessionUID uid)
        throws BrokerException
     {
         Session ses = Session.getSession(uid);
         if (ses == null)
             throw new BrokerException("No session for " + uid);
         ses.pause("PROTOCOL");
     }

     /**
      *Resume a session
      *<P>Packet:<B>START</b></p>
      *@param uid session to resume
      */
     public void resumeSession(SessionUID uid)
        throws BrokerException
     {
         Session ses = Session.getSession(uid);
         if (ses == null)
             throw new BrokerException("No session for " + uid);
         ses.resume("PROTOCOL");
     }

     /**
      *Pause a connection
      *<P>Packet:<B>STOP</b></p>
      *@param con connection to pause
      */
     public void pauseConnection(IMQConnection con)
     {
         con.stopConnection();
     }

     /**
      *Resume a connection
      *<P>Packet:<B>START</b></p>
      *@param con connection to start
      */
     public void resumeConnection(IMQConnection con)
     {
         con.startConnection();
     }

     /**
      * Browse a queue
      * <P>Packet:<b>BROWSE</b></p>
      *@param d destination to browse
      *@param sstr selector string to use (or null if none)
      *@return an ordered list of SysMessageIDs
      */
      public ArrayList browseQueue(Destination d, String sstr, IMQConnection con, boolean acc)
          throws BrokerException,SelectorFormatException
      {
          if (acc) 
             checkAccessPermission(PacketType.BROWSE, d, con);
          QBrowseHandler handler = (QBrowseHandler)
                      pr.getHandler(PacketType.BROWSE);
          return handler.getQBrowseList(d, sstr);
      }


      /**
       * Create a consumer
       * <P>Packet:<b>ADD_CONSUMER</b></p>
       *@param d Destination to create the consumer on
       *@param con Connection associated with the consumer
       *@param session session associated with the consumer
       *@param selector selector string (or null if none)
       *@param clientid clientid or null if none
       *@param durablename durable name or null if none
       *@param nolocal is NoLocal turned on (topics only)
       *@param size prefetch size (or -1 if none)
       *@param shared is this a shared connection
       *@param creator_uid a unique id to use as the creator for this consumer
       *                   which is used for indempotence (usually sysmessageid)
       *@return a consumer
       */
      public Consumer createConsumer(Destination d, IMQConnection con,
                        Session session, String selector, String clientid,
                        String durablename, boolean nolocal, int size,
                        boolean shared, String creator_uid, boolean acc, boolean useFlowControl)
        throws BrokerException, SelectorFormatException, IOException
      {
          if (acc)
              checkAccessPermission(PacketType.ADD_CONSUMER, d, con);
          ConsumerHandler handler = (ConsumerHandler)
                      pr.getHandler(PacketType.ADD_CONSUMER);
          Consumer[] c = handler.createConsumer(d.getDestinationUID(), con,
                       session, selector, clientid, durablename,
                       nolocal, size, shared, creator_uid, false, useFlowControl);
          if (c[2] != null)
              c[2].resume("Resuming from protocol");
          if (c[1] != null)
              c[1].resume("Resuming from protocol");
          return c[0];
      }
        

      /**
       * Destroys a durable subscription
       * <P>Packet:<b>DELETE_CONSUMER</b></P>
       *@param durableName durable name associated with the subscription
       *@param clientID clientID associated with the subscription
       */
      public void unsubscribe(String durableName, String clientID)
          throws BrokerException
      {
          ConsumerHandler handler = (ConsumerHandler)
                      pr.getHandler(PacketType.ADD_CONSUMER);
          handler.destroyConsumer(null, null, null,
                  durableName, clientID, null, false, false);
      }

      /**
       * Closes a consumer
       * <P>Packet:<b>DELETE_CONSUMER</b></P>
       *@param uid ConsumerUID to close.
       *@param session session associated with the consumer.
       *@param con Connection associated with the consumer (used
       *          for retrieving protocol version).
       */
      public void destroyConsumer(ConsumerUID uid, Session session,
            IMQConnection con)
          throws BrokerException
      {
          ConsumerHandler handler = (ConsumerHandler)
                      pr.getHandler(PacketType.ADD_CONSUMER);
          handler.destroyConsumer(con, session, uid,
                  null, null, null, true, false);
      }


    /**
     * End a transaction.
     * @param id  The TransactionUID to end
     * @param xid The Xid of the transaction to end. Required if transaction
     *            is an XA transaction. Must be null if it is not an XA
     *            transaction.
     * @param xaFlags  xaFlags passed on END operation. Used only if
     *                 an XA transaction.
     */
     public void endTransaction(TransactionUID id, JMQXid xid,
              Integer xaFlags)
          throws BrokerException
     {
          
          TransactionHandler handler = (TransactionHandler)
                      pr.getHandler(PacketType.START_TRANSACTION);
          TransactionState ts = handler.getTransactionList().retrieveState(id);

          handler.doEnd(PacketType.END_TRANSACTION, xid,
                  xaFlags, ts, id);

     }

    /**
     * Start a transaction.
     * @param xid The Xid of the transaction to start. Required if transaction
     *            is an XA transaction. Must be null if it is not an XA
     *            transaction.
     * @param xaFlags  xaFlags passed on START operation. Used only if
     *                 an XA transaction.
     * @param con       Connection client start packet came in on (or null if internal)
     * @param type  how rollback should be handled (e.g. only not prepared)
     * @param lifetime how long the transaction should live (0 == forever)
     * @return The TransactionUID started
     */
     public TransactionUID startTransaction(JMQXid xid,
              Integer xaFlags, AutoRollbackType type, long lifetime, IMQConnection con)
          throws BrokerException
     {
          if (DEBUG) {
          Globals.getLogger().log(Logger.INFO, "(in)ProtocolImpl.startTransaction:XID="+xid+", type="+type);
          }
          
          List conlist = con.getTransactionListThreadSafe();

          TransactionHandler handler = (TransactionHandler)
                      pr.getHandler(PacketType.START_TRANSACTION);

          // allocated a TID
          TransactionUID id = null;
          if (xaFlags == null || 
              TransactionState.isFlagSet(XAResource.TMNOFLAGS, xaFlags)) {
              id = new TransactionUID();
          } else if (xid != null) {
              TransactionList translist = handler.getTransactionList();

              id = translist.xidToUID(xid);
          
          } else { // XID is null, something is wrong
              throw new BrokerException("Invalid xid");
          }
          

          Object o = new Object();
          handler.doStart(id, conlist, con, 
                  type, xid, xid!= null, lifetime, 0,
                        xaFlags, PacketType.START_TRANSACTION, 
                        false, o.toString());
          if (DEBUG) {
          Globals.getLogger().log(Logger.INFO, "(out)ProtocolImpl.startTransaction:XID="+xid+", type="+type+", tid="+id);
          }

          return id;

     }

    /**
     * Commit a transaction.
     * @param id  The TransactionUID to commit
     * @param xid The Xid of the transaction to commit. Required if transaction
     *            is an XA transaction. Must be null if it is not an XA
     *            transaction.
     * @param xaFlags  xaFlags passed on COMMIT operation. Used only if
     *                 an XA transaction.
     * @param con       Connection client commit packet came in on (or null if internal)
     */
     public void commitTransaction(TransactionUID id, JMQXid xid,
              Integer xaFlags, IMQConnection con)
          throws BrokerException
     {
          if (DEBUG) {
          Globals.getLogger().log(Logger.INFO, "ProtocolImpl.commitTransaction:tid="+id+", XID="+xid+", xaflags="+xaFlags);
          }
          
          List conlist = con.getTransactionListThreadSafe();

          TransactionHandler handler = (TransactionHandler)
                      pr.getHandler(PacketType.START_TRANSACTION);

          if (0L == id.longValue()) {
              if (xid == null) {
                  throw new BrokerException("Unexpected TransactionUID  " + id);
              }
              id = handler.getTransactionList().xidToUID(xid);
              if (id == null) {
                  throw new BrokerException("Unknown XID " + xid, Status.NOT_FOUND);
              }
          }

          TransactionState ts = handler.getTransactionList().retrieveState(id);

          if (ts == null) {
              throw new BrokerException(
              "Unknown transaction "+id+(xid == null ? "":" XID="+xid), Status.NOT_FOUND);
          }
          if (xid != null) {
              if (ts.getXid() == null || !xid.equals(ts.getXid())) {
                  throw new BrokerException(
                  "Transaction XID mismatch "+xid+", expected "+ts.getXid()+" for transaction "+id);
              }
          }

          handler.doCommit(id, xid, xaFlags, ts, conlist, false,con, null);
     }

    /**
     * prepare a transaction.
     * @param id  The TransactionUID to prepare
     * @param xaFlags  xaFlags passed on PREPARE operation. Used only if
     *                 an XA transaction.
     */
     public void prepareTransaction(TransactionUID id, Integer xaFlags)
          throws BrokerException
     {
          TransactionHandler handler = (TransactionHandler)
                      pr.getHandler(PacketType.START_TRANSACTION);

          TransactionState ts = handler.getTransactionList().retrieveState(id);
          handler.doPrepare(id, xaFlags, ts, PacketType.PREPARE_TRANSACTION);
     }


    /**
     * Rollback a transaction
     * @param id  The TransactionUID to rollback
     * @param xid The Xid of the transaction to rollback. Required if transaction
     *            is an XA transaction. Must be null if it is not an XA
     *            transaction.
     * @param redeliver should messages be redelivered
     * @param setRedeliver if the messages are redelivered, should the redeliver 
     *                     flag be set on all messages or not
     * @param xaFlags  xaFlags passed on ROLLBACK operation. Used only if
     *                 an XA transaction.
     * @param con       Connection client rollback packet came in on (or null if internal)
     */
     public void rollbackTransaction(TransactionUID id, JMQXid xid,
              Integer xaFlags, IMQConnection con, boolean redeliver, boolean setRedeliver)
          throws BrokerException
     {
          if (DEBUG) {
          Globals.getLogger().log(Logger.INFO, "ProtocolImpl.rollbackTransaction:tid="+id+", XID="+xid+
                              ", xaflags="+xaFlags+", redeliver="+redeliver+", setRedeliver="+setRedeliver);
          }
          List conlist = con.getTransactionListThreadSafe();

          TransactionHandler handler = (TransactionHandler)
                      pr.getHandler(PacketType.START_TRANSACTION);


          if (0L == id.longValue()) {
              if (xid == null) {
                  throw new BrokerException("Unexpected TransactionUID  " + id);
              }
              id = handler.getTransactionList().xidToUID(xid);
              if (id == null) {
                  throw new BrokerException("Unknown XID " + xid, Status.NOT_FOUND);
              }
          }

          TransactionState ts = handler.getTransactionList().retrieveState(id);

          if (ts == null) {
              throw new BrokerException(
              "Unknown transaction "+id+(xid == null ? "":" XID="+xid), Status.NOT_FOUND);
          }
          if (xid != null) {
              if (ts.getXid() == null || !xid.equals(ts.getXid())) {
                  throw new BrokerException(
                  "Transaction XID mismatch "+xid+", expected "+ts.getXid()+" for transaction "+id);
              }
          }

          handler.preRollback(id, xid, xaFlags, ts);

          if (redeliver) {
              handler.redeliverUnacked(id, true, setRedeliver);
          }

          handler.doRollback(id, xid, xaFlags, ts, conlist, con,  RollbackReason.APPLICATION);
     }


     /**
      * Recover a transaction.
      *@param id id to recover or null if all
      */
     public JMQXid[] recoverTransaction(TransactionUID id)
     {
         if (DEBUG) {
         Globals.getLogger().log(Logger.INFO, "ProtocolImpl.recoverTransaction:tid="+id);
         }
         TransactionHandler handler = (TransactionHandler)
                      pr.getHandler(PacketType.START_TRANSACTION);

         TransactionList translist = handler.getTransactionList();
         Vector v = null;
         if (id == null) {
             v = translist.getTransactions(TransactionState.PREPARED);
         
         } else { // look at a single transaction
             v = new Vector();
             TransactionState ts = translist.retrieveState(id);
             if (ts.getState() == TransactionState.PREPARED) {
                 v.add(id);
             }
         }

         JMQXid xids[] = new JMQXid[v.size()];
         Iterator itr = v.iterator();
         int i = 0;
         while (itr.hasNext()) {
             TransactionUID tuid = (TransactionUID)itr.next();
             TransactionState _ts = translist.retrieveState(tuid);
             if (_ts == null) {
                 // Should never happen
                 continue;
             }
             JMQXid _xid = _ts.getXid();
             xids[i++]=_xid;

         }
         return xids;
     }

    /**
     * Verify a destination exists.
     * @param destination destination name
     * @param type DestType of the destination
     * @param selectorstr selector string to verify or null if none
     * @see com.sun.messaging.jmq.util.DestType
     * @return a hashmap which contains the following
     *  entries:<UL><LI>JMQStatus</LI><LI>JMQCanCreate</LI><LI>DestType</LI></UL>
     */
     public HashMap verifyDestination(String destination,
               int type, String selectorstr /* may be null */)
        throws BrokerException, IOException
     {

         HashMap returnmap = new HashMap();
         try {
             if (selectorstr != null) {
                Selector selector = Selector.compile(selectorstr);
                selector = null;
             }
         } catch (SelectorFormatException ex) {
              returnmap.put("JMQStatus", new Integer(Status.BAD_REQUEST));
              return returnmap;
         }
         
         Destination d =  Destination.getDestination(destination,
                       DestType.isQueue(type));

         if (d == null) {
             returnmap.put("JMQCanCreate", Boolean.valueOf(
                   Destination.canAutoCreate(
                        DestType.isQueue(type))));
             returnmap.put("JMQStatus", new Integer(Status.NOT_FOUND));
         } else {
             returnmap.put("JMQDestType", new Integer(d.getType()));
             returnmap.put("JMQStatus", new Integer(Status.OK));
         }
         return returnmap;
     }

     /**
      * Verify a transaction is PREPARED
      * @param tuid transaction id to verify
      */
     public Map verifyTransaction(TransactionUID tuid)
         throws BrokerException
     {
         TransactionHandler handler = (TransactionHandler)
                      pr.getHandler(PacketType.START_TRANSACTION);
         TransactionList translist = handler.getTransactionList();
         TransactionState ts = translist.retrieveState(tuid, true);
         if (ts == null) return null; // GONE
         int realstate = ts.getState();

         if (realstate != TransactionState.PREPARED) {
             return null; // GONE
         }
         return  translist.getTransactionMap(tuid, true);
     }


     /**
      * Redeliver messages
      */
     public void redeliver(ConsumerUID ids[], SysMessageID sysids[], 
               IMQConnection con, TransactionUID tid, boolean redeliver)
         throws BrokerException, IOException
     {
         RedeliverHandler handler = (RedeliverHandler)
                      pr.getHandler(PacketType.REDELIVER);
         handler.redeliver(ids, sysids, con, tid, redeliver);
     }


     /**
      * route, store and forward a message
      */
      public void processMessage(IMQConnection con, Packet msg)
          throws BrokerException, SelectorFormatException, IOException
      {
          DataHandler handler = (DataHandler)
                      pr.getHandler(PacketType.MESSAGE);

          Destination d = null;
          PacketReference ref = null;
          Set s = null;
          boolean route = false;
          boolean isadmin = con.isAdminConnection();
          try {
               d = Destination.getDestination(
                      msg.getDestination(), msg.getIsQueue());
               if (d == null) {
                   throw new BrokerException("Unknown Destination:" + msg.getDestination());
               }
    
               Producer pausedProducer = handler.checkFlow(msg, con);
               boolean transacted = (msg.getTransactionID() != 0);
    
                // OK generate a ref. This checks message size and
                // will be needed for later operations
                ref = handler.createReference(msg, d.getDestinationUID(), con, isadmin);
    
                // dont bother calling route if there are no messages
                //
                // to improve performance, we route and later forward
                route = handler.queueMessage(d, ref, transacted);
    
                s = handler.routeMessage(transacted, ref, route, d);
    
                // handle producer flow control
                handler.pauseProducer(d, pausedProducer, con);
    
         } catch (BrokerException ex) {
            int status = ex.getStatusCode();
            if (status == Status.ERROR && ref!= null && d != null)
                handler.cleanupOnError(d, ref);
    
            // rethrow
            throw ex;
         }
    
         if (route && d != null && s != null) {
            handler.forwardMessage(d, ref, s);
         }
     

      }


     /**
      * create a destination
      * Implemented CREATE_DESTINATION
      * @param dname name of the destination
      * @param dtype type of the destination as a bit flag from DestType
      */

    public Destination createDestination(String dname, int dtype, IMQConnection con, boolean acc)
        throws BrokerException, IOException
    {
        if (acc)
            checkAccessPermission(PacketType.CREATE_DESTINATION, dname, dtype, con);
        if (DestType.isTemporary(dtype)) {
            boolean storeTemps = con.getConnectionUID().
                            getCanReconnect();
            long reconnectTime = con.getReconnectInterval();
            Destination d = Destination.createTempDestination(
                         dname, dtype, con.getConnectionUID(), 
                         storeTemps, reconnectTime);
            if (con.getConnectionUID().equals(d.getConnectionUID())) {
                        con.attachTempDestination(d.getDestinationUID());
            }
            return d;

        }
        return Destination.getDestination(dname, dtype, true,
                   !con.isAdminConnection());
    }

    /**
     * destroy a destination
     * Implemented DESTROY_DESTINATION
     */
    public void destroyDestination(DestinationUID duid)
         throws BrokerException , IOException
    {
        Destination.removeDestination(duid, true,
               "request from protocol");
    }

    

    void checkAccessPermission(int pktType, Destination d, IMQConnection con)
                    throws AccessControlException,
                           BrokerException
    {

        checkAccessPermission(pktType, d.getName(), d.getType(), con);

    } 
    void checkAccessPermission(int pktType, String dname, int dtype, IMQConnection con)
                    throws AccessControlException, 
                           BrokerException
    {
        String op = PacketType.mapOperation(pktType);
        if (op == null) return;
        PacketHandler.checkPermission(pktType, op, dname, dtype,
                    con);
        
    }
           
}
