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

import com.sun.messaging.jmq.util.lists.*;
import com.sun.messaging.jmq.jmsserver.util.BrokerException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Hashtable;
import java.util.Vector;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.HashSet;
import java.util.Set;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.lang.ref.*;
import java.io.*;
import com.sun.messaging.jmq.io.*;
import com.sun.messaging.jmq.jmsserver.data.TransactionUID;
import com.sun.messaging.jmq.jmsserver.data.TransactionState;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.util.lists.*;
import com.sun.messaging.jmq.jmsserver.service.ConnectionUID;
import com.sun.messaging.jmq.jmsserver.service.Connection;
import com.sun.messaging.jmq.jmsserver.resources.*;
import com.sun.messaging.jmq.jmsserver.license.LicenseBase;
import com.sun.messaging.jmq.util.log.*;
import com.sun.messaging.jmq.util.JMQXid;



public class Session implements EventBroadcaster, EventListener
{
    private static boolean DEBUG = false;
    private static boolean DEBUG_CLUSTER_MSG =
                   Globals.getConfig().getBooleanProperty(
                        Globals.IMQ + ".cluster.debug.msg") || DEBUG;

    /**
     * types of consumers
     */
    public static final int AUTO_ACKNOWLEDGE = 1;
    public static final int CLIENT_ACKNOWLEDGE = 2;
    public static final int DUPS_OK_ACKNOWLEDGE = 3;
    public static final int NO_ACK_ACKNOWLEDGE= 32768;
    // NONE may be transacted or an error
    public static final int NONE = 0;

    protected Logger logger = Globals.getLogger();

    private int ackType = 0; // XXX -should really use this not consumer

    private boolean isTransacted = false;
    private boolean isXATransacted = false;
    private TransactionUID currentTransactionID = null;

    SessionUID uid;
    Map deliveredMessages;

    Map cidToStoredCid = null;

    // single session lock
    Object sessionLock = new Object();

    EventBroadcastHelper evb = new EventBroadcastHelper();

    private Map cleanupList = new HashMap();
    private Map storeMap = new HashMap();

    Map consumers = null;
    Map listeners = null;
    Set busyConsumers = null;

    boolean paused = false;
    int pausecnt = 0;
    boolean valid = false;
    private boolean busy = false;

    ConnectionUID parentCuid = null;


    transient String creator = null;


    private static boolean NOACK_ENABLED = false;
    static {
        try {
            LicenseBase license = Globals.getCurrentLicense(null);
            NOACK_ENABLED = license.getBooleanProperty(
                                license.PROP_ENABLE_NO_ACK, false);
        } catch (BrokerException ex) {
            NOACK_ENABLED = false;
        }

    }

    public static boolean isValidAckType(int type) {
        switch (type) {
            case Session.NONE: // transacted
            case Session.AUTO_ACKNOWLEDGE:
            case Session.CLIENT_ACKNOWLEDGE:
            case Session.DUPS_OK_ACKNOWLEDGE:
            case Session.NO_ACK_ACKNOWLEDGE:
                return true;
            default:
                return false;
        }
    }


    public ConnectionUID getConnectionUID() {
        return parentCuid;
    }


    public static Hashtable getAllDebugState() {
        Hashtable ht = new Hashtable();
        ht.put("TABLE", "All Sessions");
        Hashtable all = new Hashtable();
        synchronized(allSessions) {
            ht.put("allSessionCnt", String.valueOf(allSessions.size()));
            Iterator itr = allSessions.values().iterator();
            while (itr.hasNext()) {
                Session s = (Session)itr.next();
                all.put(String.valueOf(s.getSessionUID().longValue()),
                       s.getDebugState());
            }
        }
        ht.put("allSessions", all);
        all = new Hashtable();
        synchronized(ConsumerToSession) {
            ht.put("ConsumerToSession", String.valueOf(ConsumerToSession.size()));
            Iterator itr = ConsumerToSession.keySet().iterator();
            while (itr.hasNext()) {
                Object o = itr.next();
                all.put(o.toString(),
                       ConsumerToSession.get(o).toString());
            }
        }
        ht.put("ConsumerToSession", all);
        return ht;
    }

    public Hashtable getDebugState() {
        Hashtable ht = new Hashtable();
        ht.put("TABLE", "Session[" + uid.longValue() + "]");
        ht.put("uid", String.valueOf(uid.longValue()));
        ht.put("connection", String.valueOf(parentCuid.longValue()));
        ht.put("paused", String.valueOf(paused));
        ht.put("pausecnt", String.valueOf(pausecnt));
        ht.put("valid", String.valueOf(valid));
        ht.put("busy", String.valueOf(busy));
        ht.put("PendingAcks(deliveredMessages)", String.valueOf(deliveredMessages.size()));

        // ok deal w/ unacked per consumer - its easier at this level

        if (deliveredMessages.size() > 0) {
            HashMap copyDelivered = null;
            ArrayList copyCuids = null;
            int cuidCnt[] = null;
            synchronized (deliveredMessages) {
                copyDelivered = new HashMap(deliveredMessages);
            }
            synchronized (consumers) {
                copyCuids = new ArrayList(consumers.keySet());
            }
            cuidCnt = new int[copyCuids.size()];
    
            Iterator itr = copyDelivered.values().iterator();
            while (itr.hasNext()) {
                ackEntry e = (ackEntry)itr.next();
                int indx = copyCuids.indexOf(e.getConsumerUID());
                if (indx == -1)
                    continue;
                 else
                     cuidCnt[indx] ++;
            }
            Hashtable m = new Hashtable();
            for (int i=0; i < copyCuids.size(); i ++ ) {
                if (cuidCnt[i] == 0) continue;
                ConsumerUID cuid = (ConsumerUID) copyCuids.get(i);
                m.put(String.valueOf(cuid.longValue()), String.valueOf(cuidCnt[i]));
            }
            if (!m.isEmpty())
                ht.put("PendingAcksByConsumer", m);
        }


        ht.put("consumerCnt", String.valueOf(consumers.size()));
        Vector v = new Vector();
        synchronized (consumers) {
            Iterator itr = consumers.keySet().iterator();
            while (itr.hasNext()) {
                ConsumerUID cuid = (ConsumerUID)itr.next();
                v.add(String.valueOf(cuid.longValue()));
            }
        }
        ht.put("consumers", v);
        ht.put("busyConsumerCnt", String.valueOf(busyConsumers.size()));
        v = new Vector();
        synchronized (busyConsumers) {
            Iterator itr = busyConsumers.iterator();
            while (itr.hasNext()) {
                ConsumerUID cuid = (ConsumerUID)itr.next();
                v.add(String.valueOf(cuid.longValue()));
            }
        }
        ht.put("busyConsumers", v);
        return ht;
    }




    public Vector getDebugMessages(boolean full) {
        Vector v = new Vector();
        synchronized (deliveredMessages) {
            Iterator itr = deliveredMessages.values().iterator();
            while (itr.hasNext()) {
                ackEntry e = (ackEntry)itr.next();
                v.add(e.getDebugMessage(full));
            }
        }
        return v;
    }


    // used for JMX
    public int getNumPendingAcks(ConsumerUID uid)
    {
         return getPendingAcks(uid).size();
    }
    

    public List getPendingAcks(ConsumerUID uid)
    {
        List acks = new ArrayList();
        Map copyDelivered = new HashMap();
        synchronized (deliveredMessages) {
            if (deliveredMessages.size() == 0)
                   return acks;
            copyDelivered.putAll(deliveredMessages);
        }

        Iterator itr = copyDelivered.values().iterator();
        while (itr.hasNext()) {
            ackEntry e = (ackEntry)itr.next();
            if (e.getConsumerUID().equals(uid)) {
                acks.add(e.getSysMessageID());
            }
        }
        return acks;
    }



    public void setAckType(int type) 
        throws BrokerException
    {
        if (!Session.isValidAckType(type))

            throw new BrokerException(
                        "Internal Error: Invalid Ack Type :" + type,
                        Status.BAD_REQUEST);

        if (type == Session.NO_ACK_ACKNOWLEDGE && !NOACK_ENABLED) {
            throw new BrokerException(
                        Globals.getBrokerResources().getKString(
                        BrokerResources.E_FEATURE_UNAVAILABLE,
                            Globals.getBrokerResources().getKString(
                                BrokerResources.M_NO_ACK_FEATURE)),
                        BrokerResources.E_FEATURE_UNAVAILABLE,
                        (Throwable) null,
                        Status.NOT_ALLOWED);
        }
        ackType = type;
    }

    public int getConsumerCnt() {
        if (consumers == null) return 0;
        return consumers.size();
    }

    public Iterator getConsumers() {
        if (consumers == null) 
            return (new ArrayList()).iterator();
        return (new ArrayList(consumers.values())).iterator();
    }

    public boolean isAutoAck(ConsumerUID uid) {
        if (isUnknown()) {
            return uid.isAutoAck();
        }
        return ackType == Session.AUTO_ACKNOWLEDGE;
    }

    public boolean isUnknown() {
        return ackType == Session.NONE;
    }

    public boolean isClientAck(ConsumerUID uid) {
        if (isUnknown()) {
            return !uid.isAutoAck() && !uid.isDupsOK();
        }
        return ackType == Session.CLIENT_ACKNOWLEDGE;
    }

    public boolean isDupsOK(ConsumerUID uid) {
        if (isUnknown()) {
            return uid.isDupsOK();
        }
        return ackType == Session.DUPS_OK_ACKNOWLEDGE;
    }

    public boolean isUnsafeAck(ConsumerUID uid) {
        return isDupsOK(uid) || isNoAck(uid);
    }

    public boolean isNoAck(ConsumerUID uid) {
        if (isUnknown()) {
            return uid.isNoAck();
        }
        return ackType == Session.NO_ACK_ACKNOWLEDGE;
    }

    public boolean isTransacted() {
        return isTransacted;
    }

    public TransactionUID getCurrentTransactionID() {
        return currentTransactionID;
    }

    public ConsumerUID getStoredIDForDetatchedConsumer(ConsumerUID cuid) {
        return (ConsumerUID)storeMap.get(cuid);
    }

    public void debug(String prefix) {
        if (prefix == null)
            prefix = "";
        logger.log(Logger.INFO,prefix + "Session " + uid);
        logger.log(Logger.INFO,"Paused " + paused);
        logger.log(Logger.INFO,"pausecnt " + pausecnt);
        logger.log(Logger.INFO,"busy " + busy);
        logger.log(Logger.INFO,"ConsumerCnt " + consumers.size());
        logger.log(Logger.INFO,"BusyConsumerCnt " + consumers.size());
        Iterator itr = consumers.values().iterator();
        while (itr.hasNext()) {
            Consumer c = (Consumer)itr.next();
            c.debug("\t");
        }

    }

    private Session(ConnectionUID uid, String sysid) {
        this(new SessionUID(), uid, sysid);
    }

    private Session(SessionUID uid, ConnectionUID cuid, String sysid ) {
        this.uid = uid;
        parentCuid = cuid;
        deliveredMessages = Collections.synchronizedMap(new LinkedHashMap());
        cidToStoredCid = Collections.synchronizedMap(new HashMap());
        consumers = Collections.synchronizedMap(new HashMap());
        listeners = Collections.synchronizedMap(new HashMap());
        busyConsumers = Collections.synchronizedSet(new LinkedHashSet());
        valid = true;
        creator = sysid;
        DEBUG = (DEBUG || logger.getLevel() <= Logger.DEBUG || DEBUG_CLUSTER_MSG);
        logger.log(Logger.DEBUG,"Created new session " + uid
              + " on connection " + cuid);
    }

    public void dump(String prefix) {
        if (prefix == null)
            prefix = "";

        logger.log(Logger.INFO,prefix + " Session " + uid);
        logger.log(Logger.INFO, prefix + "---------------------------");
        logger.log(Logger.INFO, prefix + "busyConsumers (size) " + busyConsumers.size());
        logger.log(Logger.INFO, prefix + "busyConsumers (list) " + busyConsumers);
        logger.log(Logger.INFO, prefix + "consumers (size) " + consumers.size());
        logger.log(Logger.INFO, prefix + "consumers (list) " + consumers);
        logger.log(Logger.INFO, prefix + "---------------------------");
        Iterator itr = consumers.values().iterator();
        while (itr.hasNext()) {
            ((Consumer)itr.next()).dump(prefix + "\t");
        }
    }

    class ackEntry
    {
        ConsumerUID uid = null;
        ConsumerUID storedcid = null;

        Object pref = null;
        SysMessageID id = null;
        TransactionUID tuid = null;
        int hc = 0;

        public ackEntry(SysMessageID id,
              ConsumerUID uid) 
        { 
             assert id != null;
             assert uid != null;
             this.id = id;
             this.uid = uid;
             pref = null;
        }

        public String toString() {
            return id+"["+uid+","+storedcid+"]"+ (tuid == null? "":"TUID="+tuid);
        }

        public String getDebugMessage(boolean full)
        {
            PacketReference ref = getReference();
            Packet p = (ref == null ? null : ref.getPacket());

            String str = "[" + uid + "," + storedcid + "," +
                    (p == null ? "null" : p.toString()) + "]";
            if (full && p != null) {
                 str += "\n" + p.dumpPacketString(">>");
            }
            return str;
        }

        public void setTUID(TransactionUID uid) {
            this.tuid = uid;
        }
        public TransactionUID getTUID() {
            return tuid;
        }

        public ConsumerUID getConsumerUID() {
            return uid;
        }
        public ConsumerUID getStoredUID() {
            return storedcid;
        }
        public SysMessageID getSysMessageID() {
            return id;
        }
        public PacketReference getReference() {
            if (pref instanceof WeakReference) {
                return (PacketReference)((WeakReference)pref).get();
            } else {
                return (PacketReference)pref;
            }
        }


        public ackEntry(PacketReference ref, 
               ConsumerUID uid, ConsumerUID storedUID) 
        {
            if (ref.isLocal()) {
                pref = new WeakReference(ref);
            } else {
                pref = ref;
            }
            id = ref.getSysMessageID();
            storedcid = storedUID;
            this.uid = uid;
        }

        public PacketReference acknowledged(boolean notify) throws BrokerException {
            return acknowledged(notify, null, null, true);
        }

        public PacketReference acknowledged(boolean notify, boolean ackack) throws BrokerException {
            return acknowledged(notify, null, null, ackack);
        }

        public PacketReference acknowledged(boolean notify, TransactionUID tid,
                                            HashMap remoteNotified, boolean ackack) 
            throws BrokerException
        {
            assert pref != null;
            boolean rm = false;

            PacketReference ref = getReference();

            try {
                if (ref == null || ref.isOverrided()) {
                    // XXX weird
                    ref = Destination.get(id);
                }
                if (ref == null) {
                    String emsg = null;
                    if (tid == null) {
                        emsg = Globals.getBrokerResources().getKString(
                        BrokerResources.W_ACK_MESSAGE_GONE, this.toString());
                    } else {
                        emsg = Globals.getBrokerResources().getKString(
                        BrokerResources.W_ACK_MESSAGE_GONE_IN_TXN, tid.toString(), 
                                             this.toString());
                    }
                    logger.log(Logger.WARNING, emsg);
                    throw new BrokerException(emsg, Status.CONFLICT);
                }
                if (ref.isOverrided()) {
                    BrokerException bex = new BrokerException(
                               "Message requeued:"+ref, Status.GONE);
                    bex.setRemoteConsumerUIDs(String.valueOf(getConsumerUID().longValue()));
                    bex.setRemote(true); 
                    throw bex;
                }
                rm = ref.acknowledged(uid, storedcid, !isUnsafeAck(uid), notify, tid, remoteNotified, ackack);
                Consumer c = (Consumer)consumers.get(uid);
                if (c != null)
                    c.setLastAckTime(System.currentTimeMillis());
            } catch (Exception ex) {
                assert false : ref;
                logger.logStack(Logger.DEBUG,"Unable to process acknowledgement, Ignoring", ex);
                if (ex instanceof BrokerException)
                    throw (BrokerException) ex;
                throw new BrokerException("Unable to process ack", ex);
            }
            return (rm ? ref : null);
        }

        public boolean equals(Object o) {
            if (! (o instanceof ackEntry)) {
                return false;
            }
            ackEntry ak = (ackEntry)o;
            return uid.equals(ak.uid) &&
                   id.equals(ak.id);
        }
        public int hashCode() {
            // uid is 4 bytes
            if (hc == 0) {
                hc = id.hashCode()*15 + uid.hashCode();
            }
            return hc;
        }
    }

    public SessionUID getSessionUID() {
        return uid;
    }


    public void pause(String reason) {
        synchronized(sessionLock) {
            paused = true;
            pausecnt ++;
            if (DEBUG)
                logger.log(Logger.INFO,"Session: Pausing " + this 
                    + "[" + pausecnt + "]" + reason);
        }
        checkState(null);
    }

    public void resume(String reason) {
        synchronized(sessionLock) {
            pausecnt --;
            if (pausecnt <= 0)
                paused = false;
            assert pausecnt >= 0: "Bad pause " + this;
            if (DEBUG)
                logger.log(Logger.INFO,"Session: Resuming " + this 
                     + "[" + pausecnt + "]" + reason);
        }
        checkState(null);
    }

    public boolean isPaused() {
        return paused;
    }

    public boolean hasWork() {
        return (busyConsumers.size() > 0);
    }


    public boolean fillNextPacket (Packet p, ConsumerUID cid)
    {
        if (paused) {
            return false;
        }
        Consumer consumer = (Consumer)consumers.get(cid);
        PacketReference ref = null;
        synchronized (sessionLock) {
            ref = consumer.getAndFillNextPacket(p);
            if (ref == null) return false;

            ConsumerUID sid = consumer.getStoredConsumerUID();

  
            ackEntry entry = null; 
            if (!consumer.getConsumerUID().isNoAck()) {
                entry = new ackEntry(ref, cid, 
                   sid);
                synchronized(deliveredMessages) {
                    deliveredMessages.put(entry, entry);
                }
            }

            try  {
                ConsumerUID c = consumer.getConsumerUID();
                boolean store = !isAutoAck(c) || 
                       deliveredMessages.size() == 1;
            
                if (ref.delivered(c, sid, !isUnsafeAck(c), store)) {
                    // either hit limit or need to ack message
                    Destination d = ref.getDestination();
                    //XXX - use destination limit
                    if (ref.isDead()) { // undeliverable
                       d.removeDeadMessage(ref);
                       ref = null;
                    } else { // no ack remove
                       d.removeMessage(ref.getSysMessageID(),
                           RemoveReason.ACKNOWLEDGED);
                    }
                }

            } catch (Exception ex) {
                logger.logStack(Logger.INFO,
                    "Message Gone, unable to update"
                    + " state of the message " + ref ,ex);
                synchronized(deliveredMessages) {
                    if (entry != null)
                        deliveredMessages.get(entry);
                }
            }
        }
        return ref != null;

    }

    public ConsumerUID fillNextPacket(Packet p) 
       //throws BrokerException, IOException
    {
        if (paused) {
            return null;
        }
        
        ConsumerUID cid = null;
        Consumer consumer = null;
        while (!paused) {          
            // get a consumer
            synchronized (busyConsumers) {
               if (busyConsumers.isEmpty()) {
                   break;
               }
               Iterator itr = busyConsumers.iterator();
               cid = (ConsumerUID)itr.next();
               consumer = (Consumer)consumers.get(cid);
               itr.remove();
            }

            assert p != null;

            if (consumer == null) return null;

            PacketReference ref = null;
            synchronized (sessionLock) {
                if (paused)  {
                    synchronized (busyConsumers) {
                        if (consumer.isBusy())
                            busyConsumers.add(cid);
                    }
                    return null;
                }

                ref = consumer.getAndFillNextPacket(p);
                synchronized (busyConsumers) {
                    if (consumer.isBusy())
                        busyConsumers.add(cid);
                }

                if (ref == null) {
                    continue;
                }

                // ok the consumer wrote what it could
                // now store the session info

                ConsumerUID sid = consumer.getStoredConsumerUID();

  
                ackEntry entry = null; 
                if (!consumer.getConsumerUID().isNoAck()) {
                    entry = new ackEntry(ref, cid, 
                       sid);
                    synchronized(deliveredMessages) {
                        deliveredMessages.put(entry, entry);
                    }
                }

                try  {
                    ConsumerUID c = consumer.getConsumerUID();
                    boolean store = !isAutoAck(c) || 
                           deliveredMessages.size() == 1;
              
                    if (ref.delivered(c, sid, !isUnsafeAck(c), store)) {
                        // either hit limit or need to ack message
                        Destination d = ref.getDestination();
                        //XXX - use destination limit
                        if (ref.isDead()) { // undeliverable
                           d.removeDeadMessage(ref);
                           ref = null;
                           continue;
                        } else { // no ack remove
                           d.removeMessage(ref.getSysMessageID(),
                               RemoveReason.ACKNOWLEDGED);
                        }
                    }

                } catch (Exception ex) {
                    logger.logStack(Logger.INFO,
                        "Message Gone, unable to update"
                        + " state of the message " + ref ,ex);
                    synchronized(deliveredMessages) {
                        if (entry != null)
                            deliveredMessages.get(entry);
                    }
                    continue;
                }
            }

            checkState(null);
            return (ref != null && cid != null ? cid : null);

        }
        checkState(null);
        return null;
             
    }

    public Object getBusyLock() {
        return busyConsumers;
    }

    public boolean isBusy() {
        synchronized (busyConsumers) {
            return busy;
        }
    }

    public String toString() {
        return "Session [" + uid + "]";

    }

    private Set detachedRConsumerUIDs = Collections.synchronizedSet(new LinkedHashSet());

    public synchronized void attachConsumer(Consumer c) {
        logger.log(Logger.DEBUG,"Attaching Consumer " + c.getConsumerUID()
           + " to Session " + uid);

        if (!valid) {
            throw new IllegalStateException(this + " is closed");
        }
        c.attachToSession(getSessionUID());
        ConsumerUID cuid = c.getConsumerUID();
        cuid.setAckType(ackType);
        c.getStoredConsumerUID().setAckType(ackType);
        consumers.put(cuid, c);

        Destination d = c.getFirstDestination();
        listeners.put(cuid, c.addEventListener(this, 
             EventType.BUSY_STATE_CHANGED, null));
        if (c.isBusy()) {
            busyConsumers.add(cuid);
        }
        synchronized(ConsumerToSession) {
            ConsumerToSession.put(c.getConsumerUID(),
                  getSessionUID());
        }

        checkState(null);
    }

    /**
     * clean indicated that it was made by a 3.5 consumer calling
     * close
     * @param id last SysMessageID seen (null indicates all have been seen)
     * @param redeliverAll  ignore id and redeliver all
     * @param redeliverPendingConsume - redeliver pending messages
     */
    public Consumer detatchConsumer(ConsumerUID c, SysMessageID id, 
               boolean redeliverPendingConsume, boolean redeliverAll)
        throws BrokerException
    {
        pause("Consumer.java: detatch consumer " + c);
        Consumer con = (Consumer)consumers.remove(c);
        if (con == null) {
            assert con != null;
            resume("Consumer.java: bad removal " + c);
            throw new BrokerException("Detatching consumer " + c 
                 + " not currently attached "
                  +  "to " + this );
        }
        con.pause("Consumer.java: detatch consumer " + c
             + " DEAD"); // we dont want to ever remove messages
        detatchConsumer(con, id, redeliverPendingConsume, redeliverAll);
        resume("Consumer.java: detatch consumer " + c);
        return con;
    }

    /**
     * @param id last SysMessageID seen (null indicates all have been seen)
     * @param redeliverAll  ignore id and redeliver all
     * @param redeliverPendingConsume - redeliver pending messages
     */
    private void detatchConsumer(Consumer con, SysMessageID id,
           boolean redeliverPendingConsume, boolean redeliverAll)
    {
        if (DEBUG) {
        logger.log(Logger.INFO,"Detaching Consumer "+con.getConsumerUID()+
           " on connection "+ con.getConnectionUID()+ 
           " from Session " + uid + " last id was " + id);
        }
        con.pause("Consumer.java: Detatch consumer 1 " + con   );
        pause("Consumer.java: Detatch consumer A " + con);
        ConsumerUID c = con.getConsumerUID();
        ConsumerUID sid = con.getStoredConsumerUID();
        Object listener= listeners.remove(c);
        assert listener != null;
        con.removeEventListener(listener);
        con.attachToSession(null);
        busyConsumers.remove(c);
        consumers.remove(c);
        checkState(null);

        // OK, we have 2 sets of messages:
        //    messages which were seen (and need the state
        //         set to consumed)
        //    messages which were NOT seen (and need the
        //         state reset)
        // get delivered messages
        Set s = new LinkedHashSet();
        HashMap remotePendings = new HashMap();

        boolean holdmsgs = false;

        // get all the messages for the consumer
        synchronized (deliveredMessages) {
            ackEntry startEntry = null;

            // workaround for client sending ack if acknowledged
            if (id != null) {
                ackEntry entry = new ackEntry(id, c);
                startEntry = (ackEntry)deliveredMessages.get(entry);
            }
          
            // make a copy of all of the data
            cleanupList.put(c, con.getParentList());
            storeMap.put(c, con.getStoredConsumerUID());

            // OK first loop through all of the consumed
            // messages and mark them consumed
            Iterator itr = deliveredMessages.values().iterator();
            boolean found = (startEntry == null && id != null);
            while (!redeliverAll && !found && itr.hasNext()) {
                ackEntry val = (ackEntry)itr.next();
                if (val == startEntry) { 
                     // we are done with consumed messages
                     found = true;
                }
                // see if we are for a different consumer
                //forward port 6829773
                if (!val.storedcid.equals(sid) || !val.uid.equals(c))
                    continue;
                PacketReference pr = val.getReference();
                // we know the consumer saw it .. mark it consumed
                try {
                    pr.consumed(sid, !isUnsafeAck(c), isAutoAck(c));
                } catch (Exception ex) {
                    logger.log(Logger.INFO,"Internal Error " +
                        "Unable to consume " + sid + ":" + pr, ex);
                } 
                if (redeliverPendingConsume) {
                    if (pr != null) s.add(pr);
                    itr.remove();
                    continue;
                } 
                if (this.isTransacted) { 
                    if (!Globals.getTransactionList().isConsumedInTransaction(
                                              val.getSysMessageID(), val.uid)) {
                        if (pr != null) s.add(pr);
                        itr.remove();
                        continue;
                    }
                }
                if (pr != null && !pr.isLocal() && this.valid) {
                    BrokerAddress ba = pr.getAddress();   
                    if (ba != null) {
                        List l = (List)remotePendings.get(ba);
                        if (l == null) {
                            l = new ArrayList();
                            remotePendings.put(ba, l);
                        }
                        l.add(pr.getSysMessageID());
                        detachedRConsumerUIDs.add(val.uid);	
                    }
                }
                holdmsgs = true;
            }
            // now deal with re-queueing messages
            while (itr.hasNext()) {
                ackEntry val = (ackEntry)itr.next();
                // see if we are for a different consumer
                if (!val.storedcid.equals(sid)  || !val.uid.equals(c))
                    continue;
                PacketReference pr = val.getReference();
                if (this.isTransacted) { 
                    if (Globals.getTransactionList().isConsumedInTransaction(
                                              val.getSysMessageID(), val.uid)) {
				        if (pr != null && !pr.isLocal() && this.valid) {
                            BrokerAddress ba = pr.getAddress();   
                            if (ba != null) {
                                List l = (List)remotePendings.get(ba);
                                if (l == null) {
                                    l = new ArrayList();
                                    remotePendings.put(ba, l);
                                }
                                l.add(pr.getSysMessageID());
				                detachedRConsumerUIDs.add(val.uid);	
                            }
                        }
                        holdmsgs = true;
                        continue;
                    }
                }
                if ( pr != null) {
                    s.add(pr);
                }
                itr.remove();
                try {
                    if (pr != null) {
                        pr.removeDelivered(sid, true);
                     }
                } catch (Exception ex) {
                    logger.log(Logger.INFO,"Internal Error " +
                        "Unable to consume " + sid + ":" + pr, ex);
                } 
            }
        }
        con.destroyConsumer(s, remotePendings,
                            (con.tobeRecreated() || (!valid && !isXATransacted)),
                            false, true);

        if (!holdmsgs && this.valid) {
            synchronized (deliveredMessages) {
                cleanupList.remove(c);
                storeMap.remove(c);
            }
            synchronized(ConsumerToSession) {
                ConsumerToSession.remove(c);
            }
        }

        resume("Consumer.java: resuming after detatch " + con);

    }

    public BrokerAddress acknowledgeInTransaction(ConsumerUID cuid, SysMessageID id, 
                                                  TransactionUID tuid, boolean isXA)
        throws BrokerException
    {
        //workaround client REDELIVER protocol for XA transaction
        if (!isTransacted) isTransacted = true;
        if (isXA && !isXATransacted) isXATransacted = true;
        currentTransactionID = tuid;

        // remove from the session pending list
        ackEntry entry = new ackEntry(id, cuid);
        synchronized(deliveredMessages) {
            entry = (ackEntry)deliveredMessages.get(entry);
        }
        if (entry == null) {
                String info = "Received unknown message for transaction " + tuid + " on session " + uid + " ack info is " + cuid + "," + id;
                // for debugging see if message exists
                PacketReference m = Destination.get(id);
                if (m == null) {
                    info +=": Broker does not know about the message";
                } else {
                    info += ":Broker knows about the message, not associated with the session";
                }
                // send acknowledge
                logger.log(Logger.WARNING, info);
                BrokerException bex = new BrokerException(info, Status.GONE);
                bex.setRemoteConsumerUIDs(String.valueOf(cuid.longValue()));
                bex.setRemote(true);
                throw bex;
        }
        if (entry.getTUID() != null && !entry.getTUID().equals(tuid)) {
                BrokerException bex = new BrokerException(
                "Message requeued:"+entry.getReference(), Status.GONE);
                bex.setRemoteConsumerUIDs(String.valueOf(entry.getConsumerUID().longValue()));
                bex.setRemote(true);
                throw bex;
        }
        PacketReference ref = entry.getReference();
        if (ref == null) {
            throw new BrokerException(Globals.getBrokerResources().getKString(
            BrokerResources.I_ACK_FAILED_MESSAGE_REF_CLEARED,
            ""+id+"["+cuid+":"+entry.getStoredUID()+"]TUID="+tuid), Status.CONFLICT);
        }
        if (ref.isOverrided()) {
                BrokerException bex = new BrokerException(
                "Message requeued:"+entry.getReference(), Status.GONE);
                bex.setRemoteConsumerUIDs(String.valueOf(entry.getConsumerUID().longValue()));
                bex.setRemote(true);
                throw bex;
        }
        entry.setTUID(tuid);
        return ref.getAddress();
        // return;
    }


    private void close() {
        synchronized(this) {
            if (!valid) return;
            valid = false;
        }
        logger.log(Logger.DEBUG,"Close Session " + uid);
        
        Connection conn = Globals.getConnectionManager().getConnection(getConnectionUID());
        boolean old = false;
        if (conn != null && conn.getClientProtocolVersion() < Connection.RAPTOR_PROTOCOL) {
            old =true;
        }

        Iterator itr = null;
        synchronized (this) {
            itr = new HashSet(consumers.values()).iterator();
        }
        while (itr.hasNext()) {
            Consumer c =(Consumer)itr.next();
            itr.remove();
            detatchConsumer(c, null, old, false);
        }

        // deal w/ old messages
        synchronized(deliveredMessages) {
            if (!deliveredMessages.isEmpty()) {
                // get the list by IDs
                HashMap openMsgs = new HashMap();
                itr = deliveredMessages.entrySet().iterator();
                while (itr.hasNext()) {
                    Map.Entry entry = (Map.Entry)itr.next();
                    ackEntry e = (ackEntry)entry.getValue();

                    // deal w/ orphan messages
                    TransactionUID tid = e.getTUID();
                    if (tid != null) {
                        JMQXid jmqxid = Globals.getTransactionList().UIDToXid(tid);
                        ConsumerUID cuid = e.getConsumerUID();
                        ConsumerUID storeduid = (e.getStoredUID() == null ? 
                                                      cuid:e.getStoredUID()); 
                        if (jmqxid != null) {
                            Globals.getTransactionList().addOrphanAck(
                                    tid, e.getSysMessageID(), storeduid, cuid);
                            itr.remove();
                            continue;
                        }
                        TransactionState ts = Globals.getTransactionList().retrieveState(tid, true);
                        if (ts != null && ts.getState() == TransactionState.PREPARED) {
                            Globals.getTransactionList().addOrphanAck(
                                    tid, e.getSysMessageID(), storeduid, cuid);
                            itr.remove();
                            continue;
                        }
                        if (ts != null && ts.getState() == TransactionState.COMMITTED) {
                            itr.remove();
                            continue;
                        }
                        if (ts != null && conn != null &&
                            ts.getState() == TransactionState.COMPLETE &&
                            conn.getConnectionState() >= Connection.STATE_CLOSED) {
                            String[] args = { ""+tid,
                                              TransactionState.toString(ts.getState()),
                                              getConnectionUID().toString() };
                            logger.log(Logger.INFO, Globals.getBrokerResources().getKString(
                                       BrokerResources.I_CONN_CLEANUP_KEEP_TXN, args));
                            Globals.getTransactionList().addOrphanAck(
                                    tid, e.getSysMessageID(), storeduid, cuid);
                            itr.remove();
                            continue;
                        }
                    }
                    PacketReference ref = e.getReference();
                    if (ref == null) ref = Destination.get(e.getSysMessageID());
                    if (ref != null && !ref.isLocal()) {
                        itr.remove();
                        try {
                            if ((ref = e.acknowledged(false)) != null) {
                                Destination d = ref.getDestination();
                                d.removeRemoteMessage(ref.getSysMessageID(),
                                             RemoveReason.ACKNOWLEDGED, ref);
                            }
                        } catch(Exception ex) {
                            logger.logStack(DEBUG_CLUSTER_MSG ? 
          Logger.WARNING:Logger.DEBUG, "Unable to clean up remote message "
                            + e.getDebugMessage(false), ex);
                        }
                        continue;
                    }

                    // we arent in a transaction ID .. cool 
                    // add to redeliver list
                    ConsumerUID cuid = e.getConsumerUID();
                    Set s = (Set)openMsgs.get(cuid);
                    if (s == null) {
                        s = new LinkedHashSet();
                        openMsgs.put(cuid, s);
                    }
                    s.add(e);
                }

    
                // OK .. see if we ack or cleanup
                itr = openMsgs.keySet().iterator();
                while (itr.hasNext()) {
                    ConsumerUID cuid = (ConsumerUID)itr.next();
                    Prioritized parent = (Prioritized)cleanupList.get(cuid);
                    ConsumerUID sid = (ConsumerUID)storeMap.get(cuid);
                    if (parent == null) {
                        Set s = (Set)openMsgs.get(cuid);
                        Iterator sitr = s.iterator();
                        while (sitr.hasNext()) {
                            ackEntry e = (ackEntry)sitr.next();
                            try {
                                PacketReference ref = e.acknowledged(false);
                                if (ref != null ) {
                                      Destination d= ref.getDestination();
                                      try {
                                          d.removeMessage(ref.getSysMessageID(),
                                              RemoveReason.ACKNOWLEDGED);
                                      } catch (Exception ex) {
                                          logger.logStack(Logger.INFO,"Internal Error",
                                              ex);
                                      }
                                }
                            } catch (Exception ex) {
                                // ignore
                            }
                        }
                     } else {
                        LinkedHashSet msgs = new LinkedHashSet();
                        Set s = (Set)openMsgs.get(cuid);
                        Iterator sitr = s.iterator();
                        while (sitr.hasNext()) {
                            ackEntry e = (ackEntry)sitr.next();
                            PacketReference ref = e.getReference();
                            if (ref != null) {
                                try {
                                    ref.consumed(sid, !isUnsafeAck(cuid), isAutoAck(cuid));
                                } catch (Exception ex) {
                                    logger.log(Logger.INFO,"Internal Error " +
                                       "Unable to consume " + sid + ":" + ref, ex);
                                }
                                msgs.add(ref);
                            } else {
                                sitr.remove();
                            }
                        }
                        parent.addAllOrdered(msgs);
                     }
                 }
                 deliveredMessages.clear();
                 cleanupList.clear();
                 storeMap.clear();
              }
          }

        synchronized(detachedRConsumerUIDs) {
           itr = (new LinkedHashSet(detachedRConsumerUIDs)).iterator();
        }

        if (!isXATransacted) {
            while (itr.hasNext()) {
                Consumer c =Consumer.newInstance((ConsumerUID)itr.next()); 
                try {
                Globals.getClusterBroadcast().destroyConsumer(c, null, true); 
                } catch (Exception e) {
                logger.log(Logger.WARNING, 
                       "Unable to send consumer ["+c+ 
                       "] cleanup notification for closing of session ["+ this + "].");
                }
            }
        }

          // Clear up old session to consumer match
          synchronized(ConsumerToSession) {
              Iterator citr = ConsumerToSession.values().iterator();
              while (citr.hasNext()) {
                  SessionUID suid = (SessionUID)citr.next();
                  if (suid.equals(uid)) {
                      citr.remove();
                  }

              }
          }
          allSessions.remove(uid);

    }

    /**
     * handles an undeliverable message. This means:
     * <UL>
     *   <LI>removing it from the pending ack list</LI>
     *   <LI>Sending it back to the destination to route </LI>
     * </UL>
     * If the message can not be routed, returns the packet reference
     * (to clean up)
     */
    public PacketReference handleUndeliverable(ConsumerUID cuid,
           SysMessageID id)
        throws BrokerException
    {

        Consumer c = Consumer.getConsumer(cuid);
        // get our stored UID

        ackEntry entry = new ackEntry(id, cuid);
        PacketReference ref = null;
        synchronized(deliveredMessages) {
            entry = (ackEntry)deliveredMessages.remove(entry);
        }
        if (entry == null) {
                return null;
        }
        ref = entry.getReference();
        if (ref == null) {
           // already gone
           return null;
        }
        ConsumerUID storedid = c.getStoredConsumerUID();
        if (storedid.equals(cuid)) {
            //not a durable or receiver, nothing to do
            try {
                if (ref.acknowledged(cuid,
                        storedid, false, false)) {
                    return ref;
                }
            } catch (Exception ex) {
                logger.logStack(Logger.DEBUG,"Error handling undeliverable", ex);
            }
            return null;
        }
        // handle it like an orphan message
        // this re-queues it on the durable or queue
        Destination d = ref.getDestination();
        d.forwardOrphanMessage(ref, storedid);
        return null;
    }

    /**
     * handles an undeliverable message. This means:
     * <UL>
     *   <LI>removing it from the pending ack list</LI>
     *   <LI>Sending it back to the destination to route </LI>
     * </UL>
     * If the message can not be routed, returns the packet reference
     * (to clean up)
     */
    public PacketReference handleDead(ConsumerUID cuid,
           SysMessageID id, Throwable thr, String comment, int deliverCnt)
        throws BrokerException
    {

        Consumer c = Consumer.getConsumer(cuid);
        // get our stored UID

        ackEntry entry = new ackEntry(id, cuid);
        PacketReference ref = null;
        synchronized(deliveredMessages) {
            entry = (ackEntry)deliveredMessages.remove(entry);
        }
        if (entry == null) {
                return null;
        }
        ref = entry.getReference();
        if (ref == null) {
           // already gone
           return null;
        }
        ConsumerUID storedid = c.getStoredConsumerUID();
        // handle it like an orphan message
        // this re-queues it on the durable or queue
        Destination d = ref.getDestination();
        if (ref.markDead(storedid, comment, thr,
                   RemoveReason.UNDELIVERABLE, deliverCnt, null)) {
            return ref;
        }
        return null;
    }


    /**
     * @param ackack whether client requested ackack
     */

    public PacketReference ackMessage(ConsumerUID cuid, SysMessageID id, boolean ackack)
        throws BrokerException
    {
        return ackMessage(cuid, id, null, null, ackack);
    }

    public PacketReference ackMessage(ConsumerUID cuid, SysMessageID id,
            TransactionUID tuid, HashMap remoteNotified, boolean ackack) 
        throws BrokerException
    {
        ackEntry entry = new ackEntry(id, cuid);
        PacketReference ref = null;
        synchronized(deliveredMessages) {
            entry = (ackEntry)deliveredMessages.remove(entry);
        }
        if (entry == null) {
            return null;
        }
        ref = entry.acknowledged(true, tuid, remoteNotified, ackack);
        if (isAutoAck(entry.getConsumerUID())) {
            synchronized(deliveredMessages) {
                Iterator itr = deliveredMessages.values().iterator();
                while (itr.hasNext()) {
                    ackEntry e= (ackEntry)itr.next();
                    PacketReference newref = e.getReference();
                    if (newref == null) {
                        // see if we can get it by ID
                        newref = Destination.get(id);
                        if (newref == null) {
                            logger.log(Logger.DEBUGMED,"Removing purged reference "
                                + e);
                        } else {
                            logger.log(Logger.INFO,"Weird reference behavior" +
                               newref);
                            // acknowledge it
                            try {
                                e.acknowledged(true, ackack);
                            } catch (Exception ex) {
                            }
                        }
                        itr.remove();
                    } else {
                     
                        try {
                            newref.delivered(e.getConsumerUID(),
                              e.getStoredUID(), 
                              true, newref.isStored());
                            break;
                        } catch (Exception ex) {
                            logger.logStack(Logger.INFO,
                               "Internal error, unable to deliver " + cuid
                               + ":" + ref, ex);
                        }
                    }
                }
            }
        }
        return ref;
    }

    /**
     * @param ackack whether client requested ackack
     */
    public boolean acknowledgeToMessage(ConsumerUID cuid, SysMessageID id, boolean ackack) 
        throws BrokerException
    {
        boolean removed = false;
        ackEntry entry = new ackEntry(id, cuid);
        synchronized(deliveredMessages) {
            ackEntry value = (ackEntry)deliveredMessages.get(entry);

            if (value == null)  {
                assert false : entry;
                return false;
            }

            Iterator itr = deliveredMessages.values().iterator();
            while (itr.hasNext()) {
                ackEntry val = (ackEntry)itr.next();
                PacketReference ref = val.acknowledged(true, ackack);
                if (ref != null) {
                    Destination d= ref.getDestination();
                    try {
                        d.removeMessage(ref.getSysMessageID(),
                                          RemoveReason.ACKNOWLEDGED);
                    } catch (Exception ex) {
                        logger.logStack(Logger.INFO, "Internal Error", ex);
                    }

                }
                itr.remove();
                removed = true;
                if (val.equals(value)) {
                    break;
                }
            }
        }
        return removed;
    }



    public void eventOccured(EventType type,  Reason r,
            Object target, Object oldval, Object newval, 
            Object userdata) {

        ConsumerUID cuid = ((Consumer)target).getConsumerUID();
        if (type == EventType.BUSY_STATE_CHANGED) {

            synchronized(busyConsumers) {
                Consumer c = (Consumer)consumers.get(cuid);
                if ( c != null && c.isBusy()) {
                    // busy
                    busyConsumers.add(cuid);
                }
            }
            checkState(null); // cant hold a lock, we need to prevent a
                              // deadlock

        } else  {
            assert false : " event is not valid ";
        }
            
    }


     /**
     * Request notification when the specific event occurs.
     * @param listener object to notify when the event occurs
     * @param type event which must occur for notification
     * @param userData optional data queued with the notification
     * @return an id associated with this notification
     * @throws UnsupportedOperationException if the broadcaster does not
     *          publish the event type passed in
     */
    public Object addEventListener(EventListener listener, 
                        EventType type, Object userData)
        throws UnsupportedOperationException {

        if (type != EventType.BUSY_STATE_CHANGED ) {
            throw new UnsupportedOperationException("Only " +
                "Busy and Not Busy types supported on this class");
        }
        return evb.addEventListener(listener,type, userData);
    }

    /**
     * Request notification when the specific event occurs AND
     * the reason matched the passed in reason.
     * @param listener object to notify when the event occurs
     * @param type event which must occur for notification
     * @param userData optional data queued with the notification
     * @param reason reason which must be associated with the
     *               event (or null for all events)
     * @return an id associated with this notification
     * @throws UnsupportedOperationException if the broadcaster does not
     *         support the event type or reason passed in
     */
    public Object addEventListener(EventListener listener, 
                        EventType type, Reason reason,
                        Object userData)
        throws UnsupportedOperationException
    {
        if (type != EventType.BUSY_STATE_CHANGED ) {
            throw new UnsupportedOperationException("Only " +
                "Busy and Not Busy types supported on this class");
        }
        return evb.addEventListener(listener,type, reason, userData);
    }

    /**
     * remove the listener registered with the passed in
     * id.
     * @return the listener callback which was removed
     */
    public Object removeEventListener(Object id) {
        return evb.removeEventListener(id);
    }
  
    private void checkState(Reason r) {

        boolean notify = false;
        boolean isBusy = false;
        synchronized(busyConsumers) {
            isBusy = !paused && (busyConsumers.size() > 0);
            if (isBusy != busy) {
                busy = isBusy;
                notify = true;
            }
        }
        if (notify) {
            notifyChange(EventType.BUSY_STATE_CHANGED,
                r, this, Boolean.valueOf(!isBusy), Boolean.valueOf(isBusy));
        }
       
    }

    private void notifyChange(EventType type,  Reason r, 
               Object target,
               Object oldval, Object newval) 
    {
        evb.notifyChange(type,r, target, oldval, newval);
    }

    public synchronized Consumer getConsumerOnSession(ConsumerUID uid)
    {
        return (Consumer)consumers.get(uid);
    }


// ----------------------------------------------------------------------------
// Static Methods
// ----------------------------------------------------------------------------

    static Map ConsumerToSession = new HashMap();
    static Map allSessions = new HashMap();


    public static void clearSessions()
    {
        ConsumerToSession.clear();
        allSessions.clear();
    }

    public static Session getSession(ConsumerUID uid)
    {
        SessionUID suid = null;
        synchronized(ConsumerToSession) {
            suid = (SessionUID)ConsumerToSession.get(uid);
        }
        if (suid == null) return null;
        return getSession(suid);
    }


    // used for internal errors only
    public static void dumpAll() {
        synchronized(allSessions) {
            Globals.getLogger().log(Logger.INFO,"Dumping active sessions");
            Iterator itr = allSessions.keySet().iterator();
            while (itr.hasNext()) {
                Object k = itr.next();
                Object v = allSessions.get(k);
                Globals.getLogger().log(Logger.INFO,"\t"+k+ " : " + v);
            }
        }
    }



    public static Session createSession(ConnectionUID uid, String id )
    {
        Session s = new Session(uid, id);
        synchronized(allSessions) {
            allSessions.put(s.getSessionUID(), s);
        }
        return s;
    }


    // for default session only
    public static Session createSession(SessionUID uid, ConnectionUID cuid,
                 String id)
    {
        Session s = new Session(uid, cuid, id);
        synchronized(allSessions) {
            allSessions.put(s.getSessionUID(), s);
        }
        return s;
    }


    /**
     * @param clean closeSession was called
     */
    public static void closeSession(SessionUID uid) {
        Session s = null;
        synchronized(allSessions) {
            s = (Session)allSessions.remove(uid);
        }
        if (s == null) {
            // already was closed
            return;
        }
        assert s != null;
        s.close();
    }

    public static Session getSession(SessionUID uid)
    {
        synchronized(allSessions) {
            return (Session)allSessions.get(uid);
        }
    }

    public static Session getSession(String creator)
    {
        if (creator == null) return null;

        synchronized(allSessions) {
            Iterator itr = allSessions.values().iterator();
            while (itr.hasNext()) {
                Session c = (Session)itr.next();
                if (creator.equals(c.creator))
                    return c;
            }
        }
        return null;
    }

}


