Package org.objectweb.joram.mom.proxies

Source Code of org.objectweb.joram.mom.proxies.Xid

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

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.TimerTask;
import java.util.Vector;

import org.objectweb.joram.mom.dest.AdminTopic;
import org.objectweb.joram.mom.dest.AdminTopic.DestinationDesc;
import org.objectweb.joram.mom.dest.Destination;
import org.objectweb.joram.mom.dest.Queue;
import org.objectweb.joram.mom.dest.Topic;
import org.objectweb.joram.mom.messages.Message;
import org.objectweb.joram.mom.notifications.AbortReceiveRequest;
import org.objectweb.joram.mom.notifications.AbstractReplyNot;
import org.objectweb.joram.mom.notifications.AbstractRequestNot;
import org.objectweb.joram.mom.notifications.AcknowledgeRequest;
import org.objectweb.joram.mom.notifications.AdminReplyNot;
import org.objectweb.joram.mom.notifications.BrowseReply;
import org.objectweb.joram.mom.notifications.BrowseRequest;
import org.objectweb.joram.mom.notifications.ClientMessages;
import org.objectweb.joram.mom.notifications.DenyRequest;
import org.objectweb.joram.mom.notifications.ExceptionReply;
import org.objectweb.joram.mom.notifications.FwdAdminRequestNot;
import org.objectweb.joram.mom.notifications.QueueMsgReply;
import org.objectweb.joram.mom.notifications.ReceiveRequest;
import org.objectweb.joram.mom.notifications.SubscribeReply;
import org.objectweb.joram.mom.notifications.SubscribeRequest;
import org.objectweb.joram.mom.notifications.TopicMsgsReply;
import org.objectweb.joram.mom.notifications.UnsubscribeRequest;
import org.objectweb.joram.mom.notifications.WakeUpNot;
import org.objectweb.joram.mom.util.DMQManager;
import org.objectweb.joram.mom.util.InterceptorsHelper;
import org.objectweb.joram.mom.util.MessageInterceptor;
import org.objectweb.joram.shared.DestinationConstants;
import org.objectweb.joram.shared.MessageErrorConstants;
import org.objectweb.joram.shared.admin.AdminCommandConstant;
import org.objectweb.joram.shared.admin.AdminCommandReply;
import org.objectweb.joram.shared.admin.AdminCommandRequest;
import org.objectweb.joram.shared.admin.AdminReply;
import org.objectweb.joram.shared.admin.AdminRequest;
import org.objectweb.joram.shared.admin.ClearSubscription;
import org.objectweb.joram.shared.admin.DeleteSubscriptionMessage;
import org.objectweb.joram.shared.admin.DeleteUser;
import org.objectweb.joram.shared.admin.GetDMQSettingsReply;
import org.objectweb.joram.shared.admin.GetDMQSettingsRequest;
import org.objectweb.joram.shared.admin.GetNbMaxMsgRequest;
import org.objectweb.joram.shared.admin.GetNumberReply;
import org.objectweb.joram.shared.admin.GetSubscription;
import org.objectweb.joram.shared.admin.GetSubscriptionMessage;
import org.objectweb.joram.shared.admin.GetSubscriptionMessageIds;
import org.objectweb.joram.shared.admin.GetSubscriptionMessageIdsRep;
import org.objectweb.joram.shared.admin.GetSubscriptionMessageRep;
import org.objectweb.joram.shared.admin.GetSubscriptionRep;
import org.objectweb.joram.shared.admin.GetSubscriptions;
import org.objectweb.joram.shared.admin.GetSubscriptionsRep;
import org.objectweb.joram.shared.admin.SetDMQRequest;
import org.objectweb.joram.shared.admin.SetNbMaxMsgRequest;
import org.objectweb.joram.shared.admin.SetThresholdRequest;
import org.objectweb.joram.shared.client.AbstractJmsReply;
import org.objectweb.joram.shared.client.AbstractJmsRequest;
import org.objectweb.joram.shared.client.ActivateConsumerRequest;
import org.objectweb.joram.shared.client.CnxCloseReply;
import org.objectweb.joram.shared.client.CnxCloseRequest;
import org.objectweb.joram.shared.client.CnxConnectReply;
import org.objectweb.joram.shared.client.CnxConnectRequest;
import org.objectweb.joram.shared.client.CnxStartRequest;
import org.objectweb.joram.shared.client.CnxStopRequest;
import org.objectweb.joram.shared.client.CommitRequest;
import org.objectweb.joram.shared.client.ConsumerAckRequest;
import org.objectweb.joram.shared.client.ConsumerCloseSubRequest;
import org.objectweb.joram.shared.client.ConsumerDenyRequest;
import org.objectweb.joram.shared.client.ConsumerMessages;
import org.objectweb.joram.shared.client.ConsumerReceiveRequest;
import org.objectweb.joram.shared.client.ConsumerSetListRequest;
import org.objectweb.joram.shared.client.ConsumerSubRequest;
import org.objectweb.joram.shared.client.ConsumerUnsetListRequest;
import org.objectweb.joram.shared.client.ConsumerUnsubRequest;
import org.objectweb.joram.shared.client.GetAdminTopicReply;
import org.objectweb.joram.shared.client.GetAdminTopicRequest;
import org.objectweb.joram.shared.client.JmsRequestGroup;
import org.objectweb.joram.shared.client.MomExceptionReply;
import org.objectweb.joram.shared.client.ProducerMessages;
import org.objectweb.joram.shared.client.QBrowseReply;
import org.objectweb.joram.shared.client.QBrowseRequest;
import org.objectweb.joram.shared.client.ServerReply;
import org.objectweb.joram.shared.client.SessAckRequest;
import org.objectweb.joram.shared.client.SessCreateDestReply;
import org.objectweb.joram.shared.client.SessCreateDestRequest;
import org.objectweb.joram.shared.client.SessDenyRequest;
import org.objectweb.joram.shared.client.TempDestDeleteRequest;
import org.objectweb.joram.shared.client.XACnxCommit;
import org.objectweb.joram.shared.client.XACnxPrepare;
import org.objectweb.joram.shared.client.XACnxRecoverReply;
import org.objectweb.joram.shared.client.XACnxRecoverRequest;
import org.objectweb.joram.shared.client.XACnxRollback;
import org.objectweb.joram.shared.excepts.AccessException;
import org.objectweb.joram.shared.excepts.DestinationException;
import org.objectweb.joram.shared.excepts.MomException;
import org.objectweb.joram.shared.excepts.RequestException;
import org.objectweb.joram.shared.excepts.StateException;
import org.objectweb.joram.shared.messages.MessageHelper;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.agent.Agent;
import fr.dyade.aaa.agent.AgentId;
import fr.dyade.aaa.agent.AgentServer;
import fr.dyade.aaa.agent.BagSerializer;
import fr.dyade.aaa.agent.Channel;
import fr.dyade.aaa.agent.DeleteNot;
import fr.dyade.aaa.agent.Notification;
import fr.dyade.aaa.agent.UnknownAgent;
import fr.dyade.aaa.agent.WakeUpTask;
import fr.dyade.aaa.common.Debug;
import fr.dyade.aaa.util.management.MXWrapper;

/**
* The <code>UserAgent</code> class implements the MOM proxy behaviour,
* basically forwarding client requests to MOM destinations and MOM
* destinations replies to clients.
*/
public final class UserAgent extends Agent implements UserAgentMBean, BagSerializer, ProxyAgentItf {

  /** define serialVersionUID for interoperability */
  private static final long serialVersionUID = 1L;

  public static Logger logger = Debug.getLogger(UserAgent.class.getName());

  /** the in and out interceptors list. */
  private String interceptors_in = null;
  private String interceptors_out = null;
  private transient List interceptorsOUT = null;
  private transient List interceptorsIN = null;
   
  /** period to run the cleaning task, by default 60s. */
  private long period = 60000L;
 
  /** the number of erroneous messages forwarded to the DMQ */
  private long nbMsgsSentToDMQSinceCreation = 0;

  /**
   * Returns  the period value of this queue, -1 if not set.
   *
   * @return the period value of this queue; -1 if not set.
   */
  public long getPeriod() {
    return period;
  }

  /**
   * Sets or unsets the period for this queue.
   *
   * @param period The period value to be set or -1 for unsetting previous
   *               value.
   */
  public void setPeriod(long period) {
    if (this.period != period) {
      // Schedule the task.
      WakeUpNot not = new WakeUpNot();
      not.update = true;
      Channel.sendTo(getId(), not);
      this.period = period;
    }
  }

  /**
   * Identifier of this proxy dead message queue, <code>null</code> for DMQ
   * not set.
   */
  private AgentId dmqId = null;
 
  /**
   * Returns the default DMQ for subscription of this user.
   * @return  the default DMQ for subscription of this user.
   */
  public String getDMQId() {
    if (dmqId != null) return dmqId.toString();
    return null;
  }
 
  /**
   *  Threshold above which messages are considered as undeliverable because
   * constantly denied.
   *  This value is used as default value at subscription creation.
   *  0 stands for no threshold, -1 for value not set (use server' default value).
   */
  private int threshold = -1;

  /**
   * Returns the default threshold for the subscription of this user.
   * 0 stands for no threshold, -1 for value not set.
   *
   * @return the maximum number of message if set; -1 otherwise.
   */
  public int getThreshold() {
    return threshold;
  }

  /**
   * Sets the default threshold for the subscription of this user.
   * 0 stands for no threshold, -1 for value not set.
   * 
   * @param threshold the threshold to set.
   */
  public void setThreshold(int threshold) {
    this.threshold = threshold;
  }

  /**
   *  Maximum number of Message store in subscriptions (-1 set no limit).
   *  This value is used as default value at subscription creation.
   */
  private int nbMaxMsg = -1;

  /**
   * Returns the default maximum number of message for the subscription of this user.
   * If the limit is unset the method returns -1.
   *
   * @return the maximum number of message if set; -1 otherwise.
   */
  public int getNbMaxMsg() {
    return nbMaxMsg;
  }

  /**
   * Sets the maximum number of message for the subscription of this user.
   *
   * @param nbMaxMsg the maximum number of message (-1 set no limit).
   */
  public void setNbMaxMsg(int nbMaxMsg) {
    this.nbMaxMsg = nbMaxMsg;
  }

  /**
   * Table of the proxy's <code>ClientContext</code> instances.
   * <p>
   * <b>Key:</b> context identifier<br>
   * <b>Value:</b> context
   */
  private Map contexts;

  /**
   * Table holding the <code>ClientSubscription</code> instances.
   * <p>
   * <b>Key:</b> subscription name<br>
   * <b>Value:</b> client subscription
   */

  private Map subsTable;

  /**
   * Table holding the recovered transactions branches.
   * <p>
   * <b>Key:</b> transaction identifier<br>
   * <b>Value:</b> <code>XACnxPrepare</code> instance
   */
  private Map recoveredTransactions;

  /** Counter of message arrivals from topics. */
  private long arrivalsCounter = 0;

  /**
   * Table holding the <code>TopicSubscription</code> instances.
   * <p>
   * <b>Key:</b> topic identifier<br>
   * <b>Value:</b> topic subscription
   */
  private transient Map topicsTable;

  /**
   * Table holding the subscriptions' messages.
   * <p>
   * <b>Key:</b> message identifier<br>
   * <b>Value:</b> message
   */
  private transient Map messagesTable;

  /**
   * Identifier of the active context.
   * Value -1 means that there's no active
   * context.
   */
  private transient int activeCtxId;

  /** Reference to the active <code>ClientContext</code> instance. */
  private transient ClientContext activeCtx;

  /**
   * Table that contains the user connections:
   * - key = <code>Integer</code> (connection key)
   * - value = <code></code>
   */
  private transient Hashtable connections;

  private transient Hashtable heartBeatTasks;

  /**
   * Counter of the connection keys
   */
  private int keyCounter = 0;

  private transient WakeUpTask cleaningTask;

  /** (Re)initializes the agent when (re)loading. */
  public void agentInitialize(boolean firstTime) throws Exception {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.agentInitialize(" + firstTime + ')');

    super.agentInitialize(firstTime);
    initialize(firstTime);
    if (getPeriod() > 0)
      cleaningTask = new WakeUpTask(getId(), WakeUpNot.class, getPeriod());
    try {
      MXWrapper.registerMBean(this, getMBeanName().toString());
    } catch (Exception exc) {
      logger.log(BasicLevel.DEBUG, this + " jmx failed", exc);
    }
  }

  /** Finalizes the agent before it is garbaged. */
  public void agentFinalize(boolean lastTime) {
    if (cleaningTask != null)
      cleaningTask.cancel();
    try {
      MXWrapper.unregisterMBean(getMBeanName().toString());
    } catch (Exception exc) {
      logger.log(BasicLevel.DEBUG, this + " jmx failed", exc);
    }
    super.agentFinalize(lastTime);
  }

  private StringBuffer getMBeanName() {
    StringBuffer strbuf = new StringBuffer();
    strbuf.append("Joram#").append(AgentServer.getServerId());
    strbuf.append(':');
    strbuf.append("type=User,name=").append(getName());
    return strbuf;
  }

  /**
   * Overrides the <code>Agent</code> class <code>react</code> method for
   * providing the JMS client proxy with its specific behaviour.
   * <p>
   * A JMS proxy specifically reacts to the following notifications:
   * <ul>
   * <li><code>OpenConnectionNot</code></li>
   * </ul>
   */
  public void react(AgentId from, Notification not) throws Exception {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.react(" + from + ',' + not + ')');

    // set agent no save: the default behavior is transient
    setNoSave();

    // Administration and monitoring requests:
//  if (not instanceof SetDMQRequest)
//    doReact(from, (SetDMQRequest) not);
//  else
//  if (not instanceof SetThresholdRequestNot)
//    doReact(from, (SetThresholdRequestNot) not);
//  else
//    if (not instanceof SetNbMaxMsgRequest)
//    doReact(from, (SetNbMaxMsgRequest) not);
//  else if (not instanceof GetNbMaxMsgRequestNot)
//    doReact(from, (GetNbMaxMsgRequestNot) not);
//  else if (not instanceof GetDMQSettingsRequestNot)
//    doReact(from, (GetDMQSettingsRequestNot) not);
//  else

    if (not instanceof OpenConnectionNot) {
      doReact((OpenConnectionNot) not);
    } else if (not instanceof GetConnectionNot) {
      doReact((GetConnectionNot) not);
    } else if (not instanceof CloseConnectionNot) {
      doReact((CloseConnectionNot) not);
    } else if (not instanceof ResetCollocatedConnectionsNot) {
      doReact((ResetCollocatedConnectionsNot) not);
    } else if (not instanceof SendReplyNot) {
      doReact((SendReplyNot) not);
    } else if (not instanceof RequestNot) {
      doReact((RequestNot) not);
    } else if (not instanceof ReturnConnectionNot) {
      doReact((ReturnConnectionNot) not);
    } else if (not instanceof SendRepliesNot) {
      doReact((SendRepliesNot) not);
    } else if (not instanceof ProxyRequestGroupNot) {
      doReact((ProxyRequestGroupNot) not);
    } else if (not instanceof WakeUpNot) {
      if (cleaningTask == null || ((WakeUpNot) not).update) {
        doSetPeriod(getPeriod());
      }
      if (getPeriod() > 0) {
        cleanPendingMessages(System.currentTimeMillis());
      }
    } else if (not instanceof SyncReply) {
      doReact((SyncReply) not);
    } else if (not instanceof AbstractReplyNot) {
      doFwd(from, (AbstractReplyNot) not);
    } else if (not instanceof AdminReplyNot) {
      doReact((AdminReplyNot) not);
    } else if (not instanceof UnknownAgent) {
      doReact((UnknownAgent) not);
    } else if (not instanceof FwdAdminRequestNot) {
      doReact((FwdAdminRequestNot) not);
    } else {
      super.react(from, not);
    }
  }

  private void doSetPeriod(long period) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, this + ": setPeriod(" + period + ")." + " -> task " + cleaningTask);
    if (cleaningTask == null) {
      cleaningTask = new WakeUpTask(getId(), WakeUpNot.class, period);
    } else {
      // cancel task
      cleaningTask.cancel();
      // Schedules the wake up task period.
      if (period > 0)
        cleaningTask = new WakeUpTask(getId(), WakeUpNot.class, period);
    }
  }

  /**
   * Registers and starts the <code>UserConnection</code>.
   */
  private void doReact(OpenConnectionNot not) {
    // state change, so save.
    setSave();

    if (connections == null) {
      connections = new Hashtable();
      heartBeatTasks = new Hashtable();
    }

    Integer objKey = new Integer(keyCounter);
    ConnectionContext ctx;
    if (not.getReliable()) {
      ctx = new ReliableConnectionContext(keyCounter, not.getHeartBeat());
      connections.put(objKey, ctx);
    } else {
      ctx = new StandardConnectionContext(keyCounter);
      connections.put(objKey, ctx);
    }

    if (not.getHeartBeat() > 0) {
      HeartBeatTask heartBeatTask = new HeartBeatTask(2 * not.getHeartBeat(), objKey);
      heartBeatTasks.put(objKey, heartBeatTask);
      try {
        heartBeatTask.start();
      } catch (IOException exc) {
        // Cannot schedule task, removes it from the hashtable
        heartBeatTasks.remove(objKey);
      }
    }

    // Differs the reply because the connection key counter
    // must be saved before the OpenConnectionNot returns.
    sendTo(getId(), new ReturnConnectionNot(not, ctx));
    keyCounter++;
  }

  /**
   * Differs the reply because the connection key counter
   * must be saved before the OpenConnectionNot returns.
   */
  private void doReact(ReturnConnectionNot not) {
    not.Return();
  }

  private void doReact(GetConnectionNot not) {
    int key = not.getKey();
    if (connections == null) {
      not.Throw(new Exception("Connection " + key + " not found"));
    } else {
      Integer objKey = new Integer(key);
      ReliableConnectionContext ctx = (ReliableConnectionContext) connections.get(objKey);
      if (ctx == null) {
        not.Throw(new Exception("Connection " + key + " not found"));
      } else {
        not.Return(ctx);
      }
    }
  }

  private void doReact(RequestNot not) {
    Integer key = new Integer(not.getConnectionKey());
    if (connections != null) {
      ConnectionContext ctx = (ConnectionContext) connections.get(key);
      if (ctx != null) {
        HeartBeatTask heartBeatTask = (HeartBeatTask) heartBeatTasks.get(key);
        if (heartBeatTask != null) {
          heartBeatTask.touch();
        }

        AbstractJmsRequest request = ctx.getRequest(not.getMessage());
        reactToClientRequest(key.intValue(), request);

        if (ctx.isClosed()) {
          //CnxCloseRequest request = (CnxCloseRequest) not.getMessage();
          connections.remove(key);
          HeartBeatTask hbt = (HeartBeatTask) heartBeatTasks.remove(key);
          if (hbt != null) {
            hbt.cancel();
          }
        }
      }
    }
    // else should not happen because:
    // - RequestNot is transient
    // - RequestNot always follows an OpenConnection or
    // a GetConnection
  }

  private void doReact(ProxyRequestGroupNot not) {
    RequestNot[] requests = not.getRequests();
    RequestBuffer rm = new RequestBuffer(this);
    for (int i = 0; i < requests.length; i++) {
      RequestNot req = requests[i];
      Integer key = new Integer(req.getConnectionKey());
      HeartBeatTask heartBeatTask = (HeartBeatTask) heartBeatTasks.get(key);
      if (heartBeatTask != null) {
        heartBeatTask.touch();
      }
      ConnectionContext ctx = (ConnectionContext) connections.get(key);
      if (ctx != null) {
        AbstractJmsRequest request = ctx.getRequest(req.getMessage());
        if (request instanceof ProducerMessages) {
          ProducerMessages pm = (ProducerMessages) request;
          rm.put(req.getConnectionKey(), pm);
        } else if (request instanceof JmsRequestGroup) {
          JmsRequestGroup jrg = (JmsRequestGroup) request;
          AbstractJmsRequest[] groupedRequests = jrg.getRequests();
          for (int j = 0; j < groupedRequests.length; j++) {
            if (groupedRequests[i] instanceof ProducerMessages) {
              ProducerMessages pm = (ProducerMessages) groupedRequests[i];
              rm.put(req.getConnectionKey(), pm);
            } else {
              reactToClientRequest(key.intValue(), groupedRequests[i]);
            }
          }
        } else {
          reactToClientRequest(key.intValue(), request);
        }
      }
    }
    rm.flush();
  }

  private void doReact(CloseConnectionNot not) {
    if (connections != null) {
      Integer key = new Integer(not.getKey());
      // The connection may have already been
      // explicitely closed by a CnxCloseRequest.
      if (connections.remove(key) != null) {
        reactToClientRequest(not.getKey(), new CnxCloseRequest());
        heartBeatTasks.remove(key);
      }
    }
    // else should not happen:
    // 1- CloseConnectionNot is transient
    // 2- CloseConnectionNot follows an OpenConnectionNot
    // or a GetConnectionNot
  }

  private void doReact(ResetCollocatedConnectionsNot not) {
    if (connections != null) {
      Collection values = connections.values();
      Iterator iterator = values.iterator();
      while (iterator.hasNext()) {
        Object obj = iterator.next();
        // Standard connections must be dropped.
        // Only reliable connections can be recovered.
        if (obj instanceof StandardConnectionContext) {
          ConnectionContext cc = (ConnectionContext) obj;
          reactToClientRequest(cc.getKey(), new CnxCloseRequest());
          iterator.remove();
        }
      }
    }
  }

  private void doReact(SendRepliesNot not) {
    Enumeration en = not.getReplies();
    while (en.hasMoreElements()) {
      SendReplyNot sr = (SendReplyNot) en.nextElement();
      doReact(sr);
    }
  }

  /**
   * Notification sent by local agents (destinations)
   * indicating that the proxy can reply to a client.
   *
   * @param not
   */
  private void doReact(SendReplyNot not) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.doReact(" + not + ')');
    ClientContext cc = getClientContext(not.getKey());
    if (cc != null) {
      if (cc.setReply(not.getRequestId()) == 0) {
        sendToClient(not.getKey(), new ServerReply(not.getRequestId()));
      }
    } else if (logger.isLoggable(BasicLevel.DEBUG)) {
      // Can happen if the connection is closed before the SendReplyNot
      // arrives.
      logger.log(BasicLevel.DEBUG, "UserAgent: unknown client context for " + not);
    }
  }

  /**
   * Sends a notification to the specified agent.
   *
   * @param to the identifier of the recipient agent
   * @param not the notification to send
   */
  public void sendNot(AgentId to, Notification not) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.sendNot(" + to + ',' + not + ')');
    sendTo(to, not);
  }

  /**
   * Sends a reply to the client connected through
   * the specified connection.
   *
   * @param key the key of the connection the client
   *          is connected through.
   * @param reply the reply to send to the client.
   */
  public void sendToClient(int key, AbstractJmsReply reply) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.sendToClient(" + key + ',' + reply + ')');
    Integer objKey = new Integer(key);
    if (connections != null) {
      ConnectionContext ctx = (ConnectionContext) connections.get(objKey);
      if (ctx != null) {
        // interceptors process...
        if (interceptorsOUT != null && !interceptorsOUT.isEmpty()) {
          if (reply instanceof ConsumerMessages) {
            org.objectweb.joram.shared.messages.Message m = null;
            Vector msgs = ((ConsumerMessages) reply).getMessages();
            Vector newMsgs = new Vector();
            Vector acks = new Vector();
            for (int i = 0; i < msgs.size(); i++) {
              m = (org.objectweb.joram.shared.messages.Message) msgs.elementAt(i);
              // interceptors iterator
              Iterator it = interceptorsOUT.iterator();
              while (it.hasNext()) {
                MessageInterceptor interceptor = (MessageInterceptor) it.next();
                // interceptor handle
                if (!interceptor.handle(m)) {
                  m = null;
                  break;
                }
              }
              if (m != null) {
                newMsgs.add(m);
              } else {
                acks.add(((org.objectweb.joram.shared.messages.Message) msgs.elementAt(i)).id);
              // Send the original messages to the user DMQ.
                sendToDMQ((org.objectweb.joram.shared.messages.Message) msgs.elementAt(i), MessageErrorConstants.INTERCEPTORS);
              }
            }
            if (newMsgs.size() == 0 && !msgs.isEmpty()) { 
              // ack messages
              org.objectweb.joram.shared.messages.Message msg = (org.objectweb.joram.shared.messages.Message) msgs.firstElement();
              sendNot(AgentId.fromString(msg.toId), new AcknowledgeRequest(activeCtxId, reply.getCorrelationId(), acks));
            }
            //update consumer message.
            ((ConsumerMessages) reply).setMessages(newMsgs);
          }
        }
        // push the reply
        ctx.pushReply(reply);
      }
    }
    // else may happen. Drop the reply.
  }

  /**
   * Timer task responsible for closing the connection if it has
   * not sent any requests for the duration 'timeout'.
   */
  class HeartBeatTask extends TimerTask implements Externalizable {
    /**
     * Maximum time between two requests on the connection (This value is
     * normally the double of the hear-beat period).
     */
    private transient int timeout;

    private transient Integer key;

    private transient long lastRequestDate;

    HeartBeatTask(int timeout, Integer key) {
      this.timeout = timeout;
      this.key = key;
    }

    public void run() {
      long date = System.currentTimeMillis();
      if ((date - lastRequestDate) > timeout) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "HeartBeatTask: close connection");
        ConnectionContext ctx = (ConnectionContext) connections.remove(key);
        heartBeatTasks.remove(key);
        reactToClientRequest(key.intValue(), new CnxCloseRequest());

        if (ctx != null) {
          MomException exc = new MomException(MomExceptionReply.HBCloseConnection, "Connection " + getId()
              + ':' + key + " closed");
          ctx.pushError(exc);
        }
      }
    }

    public void start() throws IOException {
      lastRequestDate = System.currentTimeMillis();
      try {
        AgentServer.getTimer().schedule(this, timeout, timeout);
      } catch (Exception exc) {
        if (logger.isLoggable(BasicLevel.WARN))
          logger.log(BasicLevel.WARN, "HeartBeatTask: cannot schedule task " + key, exc);
        throw new IOException(exc.getMessage());
      }
    }

    public void touch() {
      lastRequestDate = System.currentTimeMillis();
    }

    // This code below is only needed by HA mechanism, do not use.

    public HeartBeatTask() {
    }

    /**
     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
     */
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
      timeout = in.readInt();
      key = new Integer(in.readInt());
    }

    /**
     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
     */
    public void writeExternal(ObjectOutput out) throws IOException {
      out.writeInt(timeout);
      out.writeInt(key.intValue());
    }
  }

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

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

  /**
   * Constructs a <code>UserAgent</code> instance.
   */
  public UserAgent() {
    super(true);
    contexts = new Hashtable();
    subsTable = new Hashtable();
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, this + ": created.");
  }

  /**
   * Creates a new user proxy.
   *
   * @see ConnectionManager
   */
  public UserAgent(String name, int stamp) {
    super(name, true, stamp);
    contexts = new Hashtable();
    subsTable = new Hashtable();
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, this + ": created.");
  }

  /**
   * Returns a string representation of this user's proxy.
   */
  public String toString() {
    return "UserAgent:" + getId();
  }

  /**
   * Only call in UserAgent creation.
   *
   * @param prop properties
   */
  public void setInterceptors(Properties prop) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "--- " + this + " initInterceptors(" + prop + ')');
   
    if (prop == null) return;

    interceptors_out = (String) prop.get(AdminCommandConstant.INTERCEPTORS_OUT);
    interceptors_in = (String) prop.get(AdminCommandConstant.INTERCEPTORS_IN);
  }
 
  /**
   * (Re)initializes the proxy.
   *
   * @param firstTime
   *
   * @exception Exception  If the proxy state could not be fully retrieved,
   *              leading to an inconsistent state.
   */
  private void initialize(boolean firstTime) throws Exception {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "--- " + this + " (re)initializing...");
    topicsTable = new Hashtable();
    messagesTable = new Hashtable();
   
    setActiveCtxId(-1);
   
    // Re-initializing after a crash or a server stop.

    // interceptors
    if (interceptors_out != null) {
      interceptorsOUT = new ArrayList();
      InterceptorsHelper.addInterceptors(interceptors_out, interceptorsOUT);
    }
    if (interceptors_in != null) {
      interceptorsIN = new ArrayList();
      InterceptorsHelper.addInterceptors(interceptors_in, interceptorsIN);
    }
   
    // Browsing the pre-crash contexts:
    ClientContext activeCtx;
    AgentId destId;
    for (Iterator ctxs = contexts.values().iterator(); ctxs.hasNext();) {
      activeCtx = (ClientContext) ctxs.next();
      ctxs.remove();

      // Denying the non acknowledged messages:
      for (Iterator queueIds = activeCtx.getDeliveringQueues(); queueIds.hasNext();) {
        destId = (AgentId) queueIds.next();
        sendNot(destId, new DenyRequest(activeCtx.getId()));

        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Denies messages on queue " + destId.toString());
      }

      // Saving the prepared transactions.
      Iterator xids = activeCtx.getTxIds();
      Xid xid;
      XACnxPrepare recoveredPrepare;
      XACnxPrepare prepare;
      while (xids.hasNext()) {
        if (recoveredTransactions == null)
          recoveredTransactions = new Hashtable();

        xid = (Xid) xids.next();

        recoveredPrepare = (XACnxPrepare) recoveredTransactions.get(xid);
        prepare = activeCtx.getTxPrepare(xid);

        if (recoveredPrepare == null)
          recoveredTransactions.put(xid, prepare);
        else {
          recoveredPrepare.getSendings().addAll(prepare.getSendings());
          recoveredPrepare.getAcks().addAll(prepare.getAcks());
        }
      }

      // Deleting the temporary destinations:
      for (Iterator tempDests = activeCtx.getTempDestinations(); tempDests.hasNext();) {
        destId = (AgentId) tempDests.next();
        deleteTemporaryDestination(destId);
 
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Deletes temporary destination " + destId.toString());
      }
    }

    // Retrieving the subscriptions' messages.
    List messages = Message.loadAll(getMsgTxname());

    if (subsTable.isEmpty()) {
      // it is possible because we always save MessageSoftRef
      // so we must delete all message.
      Message.deleteAll(getMsgTxname());
    }
   
    // Browsing the pre-crash subscriptions:
    Map.Entry subEntry;
    ClientSubscription cSub;
    List topics = new ArrayList();
    TopicSubscription tSub;
    for (Iterator subs = subsTable.entrySet().iterator(); subs.hasNext();) {
      subEntry = (Map.Entry) subs.next();
      cSub = (ClientSubscription) subEntry.getValue();
      destId = cSub.getTopicId();
      if (! topics.contains(destId))
        topics.add(destId);
      // Deleting the non durable subscriptions.
      if (!cSub.getDurable()) {
        subs.remove();
        try {
          MXWrapper.unregisterMBean(getSubMBeanName((String) subEntry.getKey()));
        } catch (Exception e1) {
          if (logger.isLoggable(BasicLevel.WARN))
            logger.log(BasicLevel.WARN, "  - Problem when unregistering ClientSubscriptionMbean", e1);
        }
      }
      // Reinitializing the durable ones.
      else {
        cSub.setProxyAgent(this);
        cSub.reinitialize(messagesTable, messages, true);
        try {
          MXWrapper.registerMBean(cSub, getSubMBeanName((String) subEntry.getKey()));
        } catch (Exception e1) {
          if (logger.isLoggable(BasicLevel.WARN))
            logger.log(BasicLevel.WARN, "  - Could not register ClientSubscriptionMbean", e1);
        }
        tSub = (TopicSubscription) topicsTable.get(destId);
        if (tSub == null) {
          tSub = new TopicSubscription();
          topicsTable.put(destId, tSub);
        }
        tSub.putSubscription((String) subEntry.getKey(), cSub.getSelector());
      }
    }
    // Browsing the topics and updating their subscriptions.
    for (Iterator topicIds = topics.iterator(); topicIds.hasNext();)
      updateSubscriptionToTopic((AgentId) topicIds.next(), -1, -1);
  }

  private void setActiveCtxId(int activeCtxId) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.setActiveCtxId(" + activeCtxId + ')');
    this.activeCtxId = activeCtxId;
  }

  /**
   * Method processing clients requests.
   * <p>
   * Some of the client requests are directly forwarded, some others are
   * sent to the proxy so that their processing occurs in a transaction.
   * <p>
   * A <code>MomExceptionReply</code> wrapping a <tt>DestinationException</tt>
   * might be sent back if a target destination can't be identified.
   */
  protected void reactToClientRequest(int key, AbstractJmsRequest request) {
    try {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "--- " + this + " got " + request.getClass().getName() + " with id: "
            + request.getRequestId() + " through activeCtx: " + key);

      if (request instanceof ProducerMessages)
        reactToClientRequest(key, (ProducerMessages) request);
      else if (request instanceof ConsumerReceiveRequest)
        reactToClientRequest(key, (ConsumerReceiveRequest) request);
      else if (request instanceof ConsumerSetListRequest)
        reactToClientRequest(key, (ConsumerSetListRequest) request);
      else if (request instanceof QBrowseRequest)
        reactToClientRequest(key, (QBrowseRequest) request);
      else if (request instanceof JmsRequestGroup)
        reactToClientRequest(key, (JmsRequestGroup) request);
      else {
        doReact(key, request);  
      }
    } catch (IllegalArgumentException iE) {
      // Catching an exception due to an invalid agent identifier to
      // forward the request to:
      DestinationException dE = new DestinationException("Incorrect destination identifier: " + iE);
      sendToClient(key, new MomExceptionReply(request.getRequestId(), dE));
    } catch (RequestException exc) {
      sendToClient(key, new MomExceptionReply(request.getRequestId(), exc));
    }
  }

  /**
   * Forwards the messages sent by the client in a
   * <code>ProducerMessages</code> request as a <code>ClientMessages</code>
   * MOM request directly to a destination, and acknowledges them by sending
   * a <code>ServerReply</code> back.
   *
   * @throws RequestException The destination id is undefined
   */
  private void reactToClientRequest(int key, ProducerMessages req) throws RequestException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.reactToClientRequest(" + key + ',' + req + ')');

    AgentId destId = AgentId.fromString(req.getTarget());
    if (destId == null)
      throw new RequestException("Request to an undefined destination (null).");

    ProducerMessages pm = req;
    if (interceptorsIN != null && !interceptorsIN.isEmpty()) {
      org.objectweb.joram.shared.messages.Message m = null;
      Vector msgs = ((ProducerMessages) req).getMessages();
      Vector newMsgs = new Vector();
      for (int i = 0; i < msgs.size(); i++) {
        m = (org.objectweb.joram.shared.messages.Message) msgs.elementAt(i);
        Iterator it = interceptorsIN.iterator();
        while (it.hasNext()) {
          MessageInterceptor interceptor = (MessageInterceptor) it.next();
          if (!interceptor.handle(m)) {
            m = null;
            break;
          }
        }
        if (m != null) {
          newMsgs.add(m);
        } else {
          // send the originals messages to the user DMQ.
          sendToDMQ((org.objectweb.joram.shared.messages.Message) msgs.elementAt(i), MessageErrorConstants.INTERCEPTORS);
        }
      }
      // no message to send. Send reply to the producer.
      if (newMsgs.size() == 0 && !msgs.isEmpty()) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "UserAgent.reactToClientRequest : no message to send.");
        if (destId.getTo() == getId().getTo() && !pm.getAsyncSend()) {
          // send producer reply
          sendNot(getId(), new SendReplyNot(key, pm.getRequestId()));
        }
        return;
      }
      //update producer message.
      ((ProducerMessages) pm).setMessages(newMsgs);
      pm = req;
    }

    ClientMessages not = new ClientMessages(key, pm.getRequestId(), pm.getMessages());
    setDmq(not);

    if (destId.getTo() == getId().getTo()) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> local sending");
      not.setPersistent(false);
      not.setExpiration(0L);
      if (pm.getAsyncSend()) {
        not.setAsyncSend(true);
      }
    } else {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> remote sending");
      if (!pm.getAsyncSend()) {
        sendNot(getId(), new SendReplyNot(key, pm.getRequestId()));
      }
    }

    sendNot(destId, not);
  }

  private void sendToDMQ(org.objectweb.joram.shared.messages.Message msg, short messageError) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "sendToDMQ(" + msg + ',' + messageError + ')');
    DMQManager dmqManager = new DMQManager(dmqId, null);
    nbMsgsSentToDMQSinceCreation++;
    dmqManager.addDeadMessage(msg, messageError);
    dmqManager.sendToDMQ();
  }
 
  private void setDmq(ClientMessages not) {
    //  Setting the producer's DMQ identifier field:
    if (dmqId != null) {
      not.setDMQId(dmqId);
    } else {
      not.setDMQId(Queue.getDefaultDMQId());
    }
  }

  /**
   * Either forwards the <code>ConsumerReceiveRequest</code> request as a
   * <code>ReceiveRequest</code> directly to the target queue, or wraps it
   * and sends it to the proxy if destinated to a subscription.
   *
   * @throws RequestException Undefined (null) destination
   */
  private void reactToClientRequest(int key, ConsumerReceiveRequest req) throws RequestException {   
    if (req.getQueueMode()) {
      ReceiveRequest not = new ReceiveRequest(key, req.getRequestId(), req.getSelector(),
          req.getTimeToLive(), req.getReceiveAck(), null, 1);
      AgentId destId = AgentId.fromString(req.getTarget());
      if (destId == null)
        throw new RequestException("Request to an undefined destination (null).");

      if (destId.getTo() == getId().getTo()) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> local receiving");
        not.setPersistent(false);
        sendNot(destId, not);
      } else {
        sendNot(destId, not);
      }
    } else {
      doReact(key, req);  
    }
  }

  /**
   * Either forwards the <code>ConsumerSetListRequest</code> request as a
   * <code>ReceiveRequest</code> directly to the target queue, or wraps it
   * and sends it to the proxy if destinated to a subscription.
   *
   * @throws RequestException Undefined (null) destination
   */
  private void reactToClientRequest(int key, ConsumerSetListRequest req) throws RequestException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "ProxyImp.reactToClientRequest(" + key + ',' + req + ')');
    if (req.getQueueMode()) {
      ReceiveRequest not = new ReceiveRequest(key,
                                              req.getRequestId(),
                                              req.getSelector(),
                                              0,
                                              false,
                                              req.getMessageIdsToAck(),
                                              req.getMessageCount());   
      AgentId destId = AgentId.fromString(req.getTarget());
      if (destId == null)
        throw new RequestException("Request to an undefined destination (null).");

      if (destId.getTo() == getId().getTo()) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> local sending");
        not.setPersistent(false);
        sendNot(destId, not);
      } else {
        sendNot(destId, not);
      }
    }
    else {
      doReact(key, req);  
    }
  }

  /**
   * Forwards the client's <code>QBrowseRequest</code> request as
   * a <code>BrowseRequest</code> MOM request directly to a destination.
   *
   * @throws RequestException Undefined (null) destination
   */
  private void reactToClientRequest(int key, QBrowseRequest req) throws RequestException {
    AgentId destId = AgentId.fromString(req.getTarget());
    if (destId == null)
      throw new RequestException("Request to an undefined destination (null).");
   
    sendNot(destId, new BrowseRequest(key, req.getRequestId(), req.getSelector()));
  }
 
  private void reactToClientRequest(int key, JmsRequestGroup request) {
    AbstractJmsRequest[] requests = request.getRequests();
    RequestBuffer rm = new RequestBuffer(this);
    for (int i = 0; i < requests.length; i++) {
      if (requests[i] instanceof ProducerMessages) {
        ProducerMessages pm =(ProducerMessages) requests[i];
        rm.put(key, pm);
      } else {
        reactToClientRequest(key, requests[i]);
      }
    }
   
    rm.flush();
  }
 
  /**
   * Distributes the client requests to the appropriate reactions.
   * <p>
   * The proxy accepts the following requests:
   * <ul>
   * <li><code>GetAdminTopicRequest</code></li>
   * <li><code>CnxConnectRequest</code></li>
   * <li><code>CnxStartRequest</code></li>
   * <li><code>CnxStopRequest</code></li>
   * <li><code>SessCreateTQRequest</code></li>
   * <li><code>SessCreateTTRequest</code></li>
   * <li><code>ConsumerSubRequest</code></li>
   * <li><code>ConsumerUnsubRequest</code></li>
   * <li><code>ConsumerCloseSubRequest</code></li>
   * <li><code>ConsumerSetListRequest</code></li>
   * <li><code>ConsumerUnsetListRequest</code></li>
   * <li><code>ConsumerReceiveRequest</code></li>
   * <li><code>ConsumerAckRequest</code></li>
   * <li><code>ConsumerDenyRequest</code></li>
   * <li><code>SessAckRequest</code></li>
   * <li><code>SessDenyRequest</code></li>
   * <li><code>TempDestDeleteRequest</code></li>
   * <li><code>XACnxPrepare</code></li>
   * <li><code>XACnxCommit</code></li>
   * <li><code>XACnxRollback</code></li>
   * <li><code>XACnxRecoverRequest</code></li>
   * </ul>
   * <p>
   * A <code>JmsExceptReply</code> is sent back to the client when an
   * exception is thrown by the reaction.
   */
  private void doReact(int key, AbstractJmsRequest request) {
    try {
      // Updating the active context if the request is not a new context
      // request!
      if (! (request instanceof CnxConnectRequest))
        setCtx(key);

      if (request instanceof GetAdminTopicRequest)
        doReact(key, (GetAdminTopicRequest) request);
      else if (request instanceof CnxConnectRequest)
        doReact(key, (CnxConnectRequest) request);
      else if (request instanceof CnxStartRequest)
        doReact((CnxStartRequest) request);
      else if (request instanceof CnxStopRequest)
        doReact((CnxStopRequest) request);
      else if (request instanceof SessCreateDestRequest)
        doReact((SessCreateDestRequest) request);
      else if (request instanceof ConsumerSubRequest)
        doReact((ConsumerSubRequest) request);
      else if (request instanceof ConsumerUnsubRequest)
        doReact((ConsumerUnsubRequest) request);
      else if (request instanceof ConsumerCloseSubRequest)
        doReact((ConsumerCloseSubRequest) request);
      else if (request instanceof ConsumerSetListRequest)
        doReact((ConsumerSetListRequest) request);
      else if (request instanceof ConsumerUnsetListRequest)
        doReact((ConsumerUnsetListRequest) request);
      else if (request instanceof ConsumerReceiveRequest)
        doReact((ConsumerReceiveRequest) request);
      else if (request instanceof ConsumerAckRequest)
        doReact((ConsumerAckRequest) request);
      else if (request instanceof ConsumerDenyRequest)
        doReact((ConsumerDenyRequest) request);
      else if (request instanceof SessAckRequest)
        doReact((SessAckRequest) request);
      else if (request instanceof SessDenyRequest)
        doReact((SessDenyRequest) request);
      else if (request instanceof TempDestDeleteRequest)
        doReact((TempDestDeleteRequest) request);
      else if (request instanceof XACnxPrepare)
        doReact((XACnxPrepare) request);
      else if (request instanceof XACnxCommit)
        doReact((XACnxCommit) request);
      else if (request instanceof XACnxRollback)
        doReact((XACnxRollback) request);
      else if (request instanceof XACnxRecoverRequest)
        doReact((XACnxRecoverRequest) request);
      else if (request instanceof CnxCloseRequest)
        doReact(key, (CnxCloseRequest) request);
      else if (request instanceof ActivateConsumerRequest)
        doReact(key, (ActivateConsumerRequest) request);
      else if (request instanceof CommitRequest)
        doReact(key, (CommitRequest)request);
    } catch (MomException mE) {
      logger.log(BasicLevel.ERROR, this + " - error during request: " + request, mE);

      // Sending the exception to the client:
      doReply(new MomExceptionReply(request.getRequestId(), mE));
    } catch (Exception exc) {
      logger.log(BasicLevel.FATAL, this + " - unexpected error during request: " + request, exc);

      // Sending the exception to the client:
      doReply(new MomExceptionReply(request.getRequestId(), new MomException(exc.getMessage())));
    }
  }

  /**
   * Method implementing the reaction to a <code>GetAdminTopicRequest</code>
   * requesting the identifier of the local admin topic.
   * <p>
   * It simply sends back a <code>GetAdminTopicReply</code> holding the
   * admin topic identifier.
   *
   * @exception AccessException  If the requester is not an administrator.
   */
  private void doReact(int key, GetAdminTopicRequest req) throws AccessException {
//     if (! admin)
//       throw new AccessException("Request forbidden to a non administrator.");
    sendToClient(key, new GetAdminTopicReply(req, AdminTopic.getDefault().toString()));
  }

  /**
   * Method implementing the reaction to a <code>CnxConnectRequest</code>
   * requesting the key of the active context.
   * <p>
   * It simply sends back a <code>ConnectReply</code> holding the active
   * context's key.
   *
   * @exception DestinationException  In case of a first administrator
   *              context, if the local administration topic reference
   *              is not available.
   */
  private void doReact(int key, CnxConnectRequest req) throws DestinationException {
    // state change, so save.
    setSave();

    setActiveCtxId(key);
    activeCtx = new ClientContext(getId(), key);
    activeCtx.setProxyAgent(this);
    contexts.put(new Integer(key), activeCtx);
   
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "Connection " + key + " opened.");

    doReply(new CnxConnectReply(req, key, getId().toString()));
  }

  /**
   * Method implementing the proxy reaction to a <code>CnxStartRequest</code>
   * requesting to start a context.
   * <p>
   * This method sends the pending <code>ConsumerMessages</code> replies,
   * if any.
   */
  private void doReact(CnxStartRequest req) {
    activeCtx.setActivated(true);

    // Delivering the pending deliveries, if any:
    for (Iterator deliveries = activeCtx.getPendingDeliveries(); deliveries.hasNext();)
      doReply((AbstractJmsReply) deliveries.next());

    // Clearing the pending deliveries.
    activeCtx.clearPendingDeliveries();
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>CnxStopRequest</code> requesting to stop a context.
   * <p>
   * This method sends a <code>ServerReply</code> back.
   */
  private void doReact(CnxStopRequest req) {
    activeCtx.setActivated(false);
    doReply(new ServerReply(req));
  }

  /**
   * Method implementing the JMS proxy reaction to a <code>SessCreateDestRequest</code>
   * requesting the creation of a destination.
   * <p>
   * Creates the queue, sends it a <code>SetRightRequest</code> for granting
   * WRITE access to all, and wraps a <code>SessCreateTDReply</code> in a
   * <code>SyncReply</code> notification it sends to itself. This latest
   * action's purpose is to preserve causality.
   * <p>
   * Creates the topic, sends it a <code>SetRightRequest</code> for granting
   * WRITE access to all, and wraps a <code>SessCreateTDReply</code> in a
   * <code>SyncReply</code> notification it sends to itself. This latest
   * action's purpose is to preserve causality.
   *
   * @exception RequestException  If the destination could not be deployed.
   */
  private void doReact(SessCreateDestRequest req) throws RequestException {
    AgentId destId = null;

    // Verify if the destination exists
    DestinationDesc desc = AdminTopic.lookupDest(req.getName(), req.getType());
    if (desc == null) {
      Destination dest = null;
      if (DestinationConstants.isQueue(req.getType())) {
        // Create a local queue.
        dest = new Queue();
      } else if (DestinationConstants.isTopic(req.getType())) {
        // Create a local topic.
        dest = new Topic();
      } else {
        throw new RequestException("Could not create destination, unknown type:" + req.getType());
      }
      dest.setName(req.getName());
      dest.setAdminId(getId());
      dest.setFreeWriting(true); // Setting free WRITE right on the destination
      if (! DestinationConstants.isTemporary(req.getType()))
        dest.setFreeReading(true); // Setting free READ right on the destination
      destId = dest.getId();
      try {
        dest.deploy();
      } catch (IOException exc) {
        throw new RequestException("Could not create destination:" + exc.getMessage());
      }
      // Registers the newly created destination
      AdminTopic.registerDest(destId, (req.getName() == null) ? destId.toString() : req.getName(),
          req.getType());

      if (DestinationConstants.isTemporary(req.getType())) {
        // Registers the temporary destination in order to clean it at the end of the connection
        activeCtx.addTemporaryDestination(destId);
      }

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "UserAgent, new destination created: " + destId);
    } else {
      destId = desc.getId();
    }

    SessCreateDestReply reply = new SessCreateDestReply(req, destId.toString());
    sendNot(getId(), new SyncReply(activeCtxId, reply));
  }

  /**
   * Method implementing the JMS proxy reaction to a <code>ConsumerSubRequest</code>
   * requesting to subscribe to a topic.
   *
   * @exception StateException    If activating an already active durable subscription.
   * @exception RequestException  If the subscription parameters are not correct.
   */
  private void doReact(ConsumerSubRequest req) throws StateException, RequestException {
    AgentId topicId = AgentId.fromString(req.getTarget());
    String subName = req.getSubName();
   
    if (topicId == null)
      throw new RequestException("Cannot subscribe to an undefined topic (null).");
   
    if (subName == null)
      throw new RequestException("Unauthorized null subscription name.");
   
    boolean newTopic = ! topicsTable.containsKey(topicId);
    boolean newSub = ! subsTable.containsKey(subName);

    TopicSubscription tSub;
    ClientSubscription cSub;

    // true if a SubscribeRequest has been sent to the topic.
    boolean sent = false;

    if (newTopic) { // New topic...
      tSub = new TopicSubscription();
      topicsTable.put(topicId, tSub);
    } else { // Known topic...
      tSub = (TopicSubscription) topicsTable.get(topicId);
    }

    if (newSub) { // New subscription...
      // state change, so save.
      setSave();
      cSub = new ClientSubscription(getId(),
                                    activeCtxId,
                                    req.getRequestId(),
                                    req.getDurable(),
                                    topicId,
                                    req.getSubName(),
                                    req.getSelector(),
                                    req.getNoLocal(),
                                    dmqId,
                                    threshold,
                                    nbMaxMsg,
                                    messagesTable);
      cSub.setProxyAgent(this);
    
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "Subscription " + subName + " created.");

      subsTable.put(subName, cSub);
      try {
        MXWrapper.registerMBean(cSub, getSubMBeanName(subName));
      } catch (Exception e) {
        if (logger.isLoggable(BasicLevel.WARN))
          logger.log(BasicLevel.WARN, "  - Could not register ClientSubscriptionMbean", e);
      }
      tSub.putSubscription(subName, req.getSelector());
      sent = updateSubscriptionToTopic(topicId, activeCtxId, req.getRequestId(), req.isAsyncSubscription());
    } else { // Existing durable subscription...
      cSub = (ClientSubscription) subsTable.get(subName);

      if (cSub.getActive())
        throw new StateException("The durable subscription " + subName + " has already been activated.");

      // Updated topic: updating the subscription to the previous topic.
      boolean updatedTopic = ! topicId.equals(cSub.getTopicId());
      if (updatedTopic) {
        TopicSubscription oldTSub =
          (TopicSubscription) topicsTable.get(cSub.getTopicId());
        oldTSub.removeSubscription(subName);
        updateSubscriptionToTopic(cSub.getTopicId(), -1, -1, req.isAsyncSubscription());
      }

      // Updated selector?
      boolean updatedSelector;
      if (req.getSelector() == null && cSub.getSelector() != null)
        updatedSelector = true;
      else if (req.getSelector() != null && cSub.getSelector() == null)
        updatedSelector = true;
      else if (req.getSelector() == null && cSub.getSelector() == null)
        updatedSelector = false;
      else
        updatedSelector = ! req.getSelector().equals(cSub.getSelector());

      // Reactivating the subscription.
      cSub.reactivate(activeCtxId, req.getRequestId(), topicId, req.getSelector(), req.getNoLocal());

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "Subscription " + subName + " reactivated.");

      // Updated subscription: updating subscription to topic. 
      if (updatedTopic || updatedSelector) {
        tSub.putSubscription(subName, req.getSelector());
        sent = updateSubscriptionToTopic(topicId, activeCtxId, req.getRequestId(), req.isAsyncSubscription());
      }
    }
    // Activating the subscription.
    activeCtx.addSubName(subName);

    // Acknowledging the request, if needed.
    if (!sent)
      sendNot(getId(), new SyncReply(activeCtxId, new ServerReply(req)));
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>ConsumerSetListRequest</code> notifying the creation of a client
   * listener.
   * <p>
   * Sets the listener for the subscription, launches a delivery sequence.
   *
   * @exception DestinationException  If the subscription does not exist.
   */
  private void doReact(ConsumerSetListRequest req) throws DestinationException {
    // Getting the subscription:
    String subName = req.getTarget();
    ClientSubscription sub = null;
    if (subName != null)
      sub = (ClientSubscription) subsTable.get(subName);

    if (sub == null)
      throw new DestinationException("Can't set a listener on the non existing subscription: " + subName);

    sub.setListener(req.getRequestId());

    ConsumerMessages consM = sub.deliver();
    if (consM != null) {
      if (activeCtx.getActivated())
        doReply(consM);
      else
        activeCtx.addPendingDelivery(consM);
    }
  }
  
  /**
   * Method implementing the JMS proxy reaction to a
   * <code>ConsumerUnsetListRequest</code> notifying that a consumer listener
   * is unset.
   *
   * @exception DestinationException  If the subscription does not exist.
   */
  private void doReact(ConsumerUnsetListRequest req) throws DestinationException {
    // If the listener was listening to a queue, cancelling any pending reply:
    if (req.getQueueMode()) {
      activeCtx.cancelReceive(req.getCancelledRequestId());
      AgentId to = AgentId.fromString(req.getTarget());
      sendNot(to,
          new AbortReceiveRequest(activeCtx.getId(), req.getRequestId(), req.getCancelledRequestId()));
    }
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>ConsumerCloseSubRequest</code> requesting to deactivate a durable
   * subscription.
   *
   * @exception DestinationException  If the subscription does not exist.
   */
  private void doReact(ConsumerCloseSubRequest req) throws DestinationException {
    // Getting the subscription:
    String subName = req.getTarget();
    ClientSubscription sub = null;
    if (subName != null)
      sub = (ClientSubscription) subsTable.get(subName);

    if (sub == null)
      throw new DestinationException("Can't desactivate non existing subscription: " + subName);

    // De-activating the subscription:
    activeCtx.removeSubName(subName);
    sub.deactivate();

    // Acknowledging the request:
    doReply(new ServerReply(req));
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>ConsumerUnsubRequest</code> requesting to remove a subscription.
   *
   * @exception DestinationException  If the subscription does not exist.
   */
  private void doReact(ConsumerUnsubRequest req) throws DestinationException {
    // state change, so save.
    setSave();

    // Getting the subscription.
    String subName = req.getTarget();
    ClientSubscription sub = null;
    if (subName != null)
      sub = (ClientSubscription) subsTable.get(subName);
    if (sub == null)
      throw new DestinationException("Can't unsubscribe non existing subscription: " + subName);

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "Deleting subscription " + subName);

    // Updating the proxy's subscription to the topic.
    AgentId topicId = sub.getTopicId();
    TopicSubscription tSub = (TopicSubscription) topicsTable.get(topicId);
    tSub.removeSubscription(subName);
    updateSubscriptionToTopic(topicId, -1, -1);

    // Deleting the subscription.
    sub.delete();
    activeCtx.removeSubName(subName);
    subsTable.remove(subName);
    try {
      MXWrapper.unregisterMBean(getSubMBeanName(subName));
    } catch (Exception e) {
      if (logger.isLoggable(BasicLevel.WARN))
        logger.log(BasicLevel.WARN, "  - Problem when unregistering ClientSubscriptionMbean", e);
    }

    // Acknowledging the request:
    sendNot(getId(), new SyncReply(activeCtxId, new ServerReply(req)));
  }

  /**
   * Method implementing the proxy reaction to a
   * <code>ConsumerReceiveRequest</code> instance, requesting a message from a
   * subscription.
   * <p>
   * This method registers the request and launches a delivery sequence.
   *
   * @exception DestinationException  If the subscription does not exist.
   */
  private void doReact(ConsumerReceiveRequest req) throws DestinationException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.doReact(" + req + ')');

    String subName = req.getTarget();
    ClientSubscription sub = null;
    if (subName != null)
      sub = (ClientSubscription) subsTable.get(subName);

    if (sub == null)
      throw new DestinationException("Can't request a message from the unknown subscription: " + subName);

    // Getting a message from the subscription.
    sub.setReceiver(req.getRequestId(), req.getTimeToLive());
    ConsumerMessages consM = sub.deliver();

    if (consM != null && req.getReceiveAck()) {
      Vector messageList = consM.getMessages();
      for (int i = 0; i < messageList.size(); i++) {
        Message msg = (Message)messageList.elementAt(i);
        sub.acknowledge(msg.getIdentifier());
      }
    }

    // Nothing to deliver but immediate delivery request: building an empty
    // reply.
    if (consM == null && req.getTimeToLive() == -1) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> immediate delivery");
      sub.unsetReceiver();
      consM = new ConsumerMessages(req.getRequestId(), subName, false);
    }
   
    // Delivering.
    if (consM != null && activeCtx.getActivated()) {
      doReply(consM);
    } else if (consM != null) {
      activeCtx.addPendingDelivery(consM);
    }
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>SessAckRequest</code> acknowledging messages either on a queue
   * or on a subscription.
   */
  private void doReact(SessAckRequest req) {
    if (req.getQueueMode()) {
      AgentId qId = AgentId.fromString(req.getTarget());
      Vector ids = req.getIds();

      AcknowledgeRequest not = new AcknowledgeRequest(activeCtxId, req.getRequestId(), ids);
      if (qId.getTo() == getId().getTo()) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> local acking");
        not.setPersistent(false);
      }

      sendNot(qId, not);
    } else {
      String subName = req.getTarget();
      ClientSubscription sub = (ClientSubscription) subsTable.get(subName);
      if (sub != null)
        sub.acknowledge(req.getIds().iterator());
    }
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>SessDenyRequest</code> denying messages either on a queue or on
   * a subscription.
   */
  private void doReact(SessDenyRequest req) {
    if (req.getQueueMode()) {
      AgentId qId = AgentId.fromString(req.getTarget());
      Vector ids = req.getIds();
      sendNot(qId, new DenyRequest(activeCtxId, req.getRequestId(), ids));

      // Acknowledging the request unless forbidden:
      if (!req.getDoNotAck())
        sendNot(getId(), new SyncReply(activeCtxId, new ServerReply(req)));
    } else {
      String subName = req.getTarget();
      ClientSubscription sub = (ClientSubscription) subsTable.get(subName);

      if (sub == null)
        return;

      sub.deny(req.getIds().iterator());

      // Launching a delivery sequence:
      ConsumerMessages consM = sub.deliver();
      // Delivering.
      if (consM != null && activeCtx.getActivated())
        doReply(consM);
      else if (consM != null)
        activeCtx.addPendingDelivery(consM);
    }
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>ConsumerAckRequest</code> acknowledging a message either on a queue
   * or on a subscription.
   */
  private void doReact(ConsumerAckRequest req) {
    if (req.getQueueMode()) {
      AgentId qId = AgentId.fromString(req.getTarget());
      AcknowledgeRequest not = new AcknowledgeRequest(activeCtxId, req.getRequestId(), req.getIds());
      if (qId.getTo() == getId().getTo()) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> local acking");
        not.setPersistent(false);
        sendNot(qId, not);
      } else {
        sendNot(qId, not);
      }
    } else {
      String subName = req.getTarget();
      ClientSubscription sub = (ClientSubscription) subsTable.get(subName);
      if (sub != null) {
        sub.acknowledge(req.getIds().iterator());
      }
    }
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>ConsumerDenyRequest</code> denying a message either on a queue
   * or on a subscription.
   * <p>
   * This request is acknowledged when destinated to a queue.
   */
  private void doReact(ConsumerDenyRequest req) {
    if (req.getQueueMode()) {
      AgentId qId = AgentId.fromString(req.getTarget());
      String id = req.getId();
      sendNot(qId, new DenyRequest(activeCtxId, req.getRequestId(), id));

      // Acknowledging the request, unless forbidden:
      if (!req.getDoNotAck())
        sendNot(getId(), new SyncReply(activeCtxId, new ServerReply(req)));
    } else {
      String subName = req.getTarget();
      ClientSubscription sub = (ClientSubscription) subsTable.get(subName);

      if (sub == null)
        return;

      Vector ids = new Vector();
      ids.add(req.getId());
      sub.deny(ids.iterator());

      // Launching a delivery sequence:
      ConsumerMessages consM = sub.deliver();
      // Delivering.
      if (consM != null && activeCtx.getActivated())
        doReply(consM);
      else if (consM != null)
        activeCtx.addPendingDelivery(consM);
    }
  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>TempDestDeleteRequest</code> request for deleting a temporary
   * destination.
   * <p>
   * This method sends a <code>fr.dyade.aaa.agent.DeleteNot</code> to the
   * destination and acknowledges the request.
   */
  private void doReact(TempDestDeleteRequest req) {
    // Removing the destination from the context's list:
    AgentId tempId = AgentId.fromString(req.getTarget());
    activeCtx.removeTemporaryDestination(tempId);

    // Sending the request to the destination:
    deleteTemporaryDestination(tempId);

    // Acknowledging the request:
    sendNot(getId(), new SyncReply(activeCtxId, new ServerReply(req)));
  }

  private void deleteTemporaryDestination(AgentId destId) {
    sendNot(destId, new DeleteNot());
    AdminTopic.unregisterDest(destId.toString());
  }

  /**
   * Method implementing the JMS proxy reaction to an
   * <code>XACnxPrepare</code> request holding messages and acknowledgements
   * produced in an XA transaction.
   *
   * @exception StateException  If the proxy has already received a prepare
   *                              order for the same transaction.
   */
  private void doReact(XACnxPrepare req) throws StateException {
    try {
      Xid xid = new Xid(req.getBQ(), req.getFI(), req.getGTI());
      activeCtx.registerTxPrepare(xid, req);
      doReply(new ServerReply(req));
    } catch (Exception exc) {
      throw new StateException(exc.getMessage());
    }
  }

  /**
   * Method implementing the JMS proxy reaction to an
   * <code>XACnxCommit</code> request committing the operations performed
   * in a given transaction.
   * <p>
   * This method actually processes the objects sent at the prepare phase,
   * and acknowledges the request.
   *
   * @exception StateException  If committing an unknown transaction.
   */
  private void doReact(XACnxCommit req) throws StateException {
    Xid xid = new Xid(req.getBQ(), req.getFI(), req.getGTI());

    XACnxPrepare prepare = activeCtx.getTxPrepare(xid);

    if (prepare == null)
      throw new StateException("Unknown transaction identifier.");

    Vector sendings = prepare.getSendings();
    Vector acks = prepare.getAcks();

    ProducerMessages pM;
    ClientMessages not;
    while (!sendings.isEmpty()) {
      pM = (ProducerMessages) sendings.remove(0);
      not = new ClientMessages(activeCtxId, pM.getRequestId(), pM.getMessages());
      sendNot(AgentId.fromString(pM.getTarget()), not);
    }

    while (!acks.isEmpty())
      doReact((SessAckRequest) acks.remove(0));

    doReply(new ServerReply(req));
  }

  /**
   * Method implementing the JMS proxy reaction to an
   * <code>XACnxRollback</code> request rolling back the operations performed
   * in a given transaction.
   */
  private void doReact(XACnxRollback req) {
    Xid xid = new Xid(req.getBQ(), req.getFI(), req.getGTI());

    String queueName;
    AgentId qId;
    Vector ids;
    for (Enumeration queues = req.getQueues(); queues.hasMoreElements();) {
      queueName = (String) queues.nextElement();
      qId = AgentId.fromString(queueName);
      ids = req.getQueueIds(queueName);
      sendNot(qId, new DenyRequest(activeCtxId, req.getRequestId(), ids));
    }

    String subName;
    ClientSubscription sub;
    ConsumerMessages consM;
    for (Enumeration subs = req.getSubs(); subs.hasMoreElements();) {
      subName = (String) subs.nextElement();
      sub = (ClientSubscription) subsTable.get(subName);
      if (sub != null) {
        sub.deny(req.getSubIds(subName).iterator());

        consM = sub.deliver();
        if (consM != null && activeCtx.getActivated())
          doReply(consM);
        else if (consM != null)
          activeCtx.addPendingDelivery(consM);
      }
    }

   XACnxPrepare prepare = activeCtx.getTxPrepare(xid);

    if (prepare != null) {
      Vector acks = prepare.getAcks();

      SessAckRequest ack;
      while (! acks.isEmpty()) {
        ack = (SessAckRequest) acks.remove(0);
        doReact(new SessDenyRequest(ack.getTarget(),
                                    ack.getIds(),
                                    ack.getQueueMode(),
                                    true));
      }
    }

    sendNot(getId(), new SyncReply(activeCtxId, new ServerReply(req)));
  }

  /**
   * Reacts to a <code>XACnxRecoverRequest</code> request requesting the
   * identifiers of the prepared transactions.
   * <p>
   * Returns the identifiers of the recovered transactions, puts the prepared
   * data into the active context for future commit or rollback.
   *
   * @exception StateException  If a recovered transaction branch is already
   *                              present in the context.
   */
  private void doReact(XACnxRecoverRequest req)
    throws StateException {
    // state change, so save.
    setSave();

    Vector bqs = new Vector();
    Vector fis = new Vector();
    Vector gtis = new Vector();
    if (recoveredTransactions != null) {
      Iterator txs = recoveredTransactions.entrySet().iterator();
      Xid xid;
      while (txs.hasNext()) {
        Map.Entry txEntry = (Map.Entry) txs.next();
        xid = (Xid) txEntry.getKey();
        bqs.add(xid.bq);
        fis.add(new Integer(xid.fi));
        gtis.add(xid.gti);
        try {
          txs.remove();
          activeCtx.registerTxPrepare(xid, (XACnxPrepare) txEntry.getValue());
        }
        catch (Exception exc) {
          throw new StateException("Recovered transaction branch has already been prepared by the RM.");
        }
      }
    }
    recoveredTransactions = null;
    doReply(new XACnxRecoverReply(req, bqs, fis, gtis));
  }

//  /**
//   * Method implementing the reaction to a <code>SetDMQRequest</code>
//   * instance setting the dead message queue identifier for this proxy
//   * and its subscriptions.
//   */
//  private void doReact(AgentId from, SetDMQRequest not) {
//    // state change, so save.
//    setSave();
//   
//    dmqId = not.getDmqId();
//
//    for (Enumeration keys = subsTable.keys(); keys.hasMoreElements();)
//      ((ClientSubscription) subsTable.get(keys.nextElement())).setDMQId(dmqId);
//
//    sendNot(from, new AdminReplyNot(not, true, "DMQ set: " + dmqId));
//  }

//  /**
//   * Method implementing the reaction to a <code>SetThreshRequest</code>
//   * instance setting the threshold value for this proxy and its
//   * subscriptions.
//   */
//  private void doReact(AgentId from, SetThresholdRequestNot not) {
//    // state change, so save.
//    setSave();
//   
//    threshold = not.getThreshold();
//
//    for (Enumeration keys = subsTable.keys(); keys.hasMoreElements();)
//      ((ClientSubscription)
//         subsTable.get(keys.nextElement())).setThreshold(not.getThreshold());
//
//    sendNot(from,
//                       new AdminReplyNot(not,
//                                      true,
//                                      "Threshold set: " + threshold));
//  }

//  /**
//   * Method implementing the reaction to a <code>SetNbMaxMsgRequest</code>
//   * instance setting the NbMaxMsg value for the subscription.
//   */
//  protected void doReact(AgentId from, SetNbMaxMsgRequest not) { XXX
//    int nbMaxMsg = not.getNbMaxMsg();
//    String subName = not.getSubName();
//
//    ClientSubscription sub = (ClientSubscription) subsTable.get(subName);
//    if (sub != null) {
//      sub.setNbMaxMsg(nbMaxMsg);
//      sendNot(from,
//                         new AdminReplyNot(not,
//                                        true,
//                                        "NbMaxMsg set: " + nbMaxMsg + " on " + subName));
//    } else {
//      sendNot(from,
//                         new AdminReplyNot(not,
//                                        false,
//                                        "NbMaxMsg not set: " + nbMaxMsg + " on " + subName));
//    }
//  }

//  /**
//   * Method implementing the reaction to a <code>Monit_GetDMQSettings</code>
//   * instance requesting the DMQ settings of this proxy.
//   */
//  private void doReact(AgentId from, GetDMQSettingsRequestNot not)
//  {
//    String id = null;
//    if (dmqId != null)
//      id = dmqId.toString();
//    sendNot(from, new GetDMQSettingsReplyNot(not, id, threshold));
//  }

  /**
   * Method implementing the JMS proxy reaction to a
   * <code>SyncReply</code> notification sent by itself, wrapping a reply
   * to be sent to a client.
   */
  private void doReact(SyncReply not) {
    sendToClient(not.key, not.reply);
  }

  /**
   * The method closes a given context by denying the non acknowledged messages
   * delivered to this context, and deleting its temporary subscriptions and
   * destinations.
   */
  private void doReact(int key, CnxCloseRequest req) {
    // state change, so save.
    setSave();

    //setCtx(cKey);

    // Denying the non acknowledged messages:
    AgentId id;
    boolean prepared = false;
    for (Iterator ids = activeCtx.getDeliveringQueues(); ids.hasNext();) {
      id = (AgentId) ids.next();

      for (Iterator xids = activeCtx.getTxIds(); xids.hasNext();) {
        Xid xid = (Xid) xids.next();
        if (activeCtx.isPrepared(xid)) {
          prepared = true;
          break;
        }
      }
      if (!prepared)
        sendNot(id, new DenyRequest(key));
      prepared = false;
    }

    // Removing or deactivating the subscriptions:
    String subName = null;
    ClientSubscription sub;
    List topics = new Vector();
    for (Iterator subs = activeCtx.getActiveSubs(); subs.hasNext();) {
      subName = (String) subs.next();
      sub = (ClientSubscription) subsTable.get(subName);

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG,
            "Deactivate subscription " + subName + ", topic id = " + sub.getTopicId());

      if (sub.getDurable()) {
        sub.deactivate();

        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Durable subscription" + subName + " de-activated.");
      } else {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> topicsTable = " + topicsTable);

        sub.delete();
        subsTable.remove(subName);
        try {
          MXWrapper.unregisterMBean(getSubMBeanName(subName));
        } catch (Exception e) {
          if (logger.isLoggable(BasicLevel.WARN))
            logger.log(BasicLevel.WARN, "  - Problem when unregistering ClientSubscriptionMbean", e);
        }
        TopicSubscription tSub = (TopicSubscription) topicsTable.get(sub
            .getTopicId());
        tSub.removeSubscription(subName);

        if (!topics.contains(sub.getTopicId()))
          topics.add(sub.getTopicId());

        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, "Temporary subscription" + subName + " deleted.");
      }
    }
    // Browsing the topics which at least have one subscription removed.
    for (Iterator topicIds = topics.iterator(); topicIds.hasNext();)
      updateSubscriptionToTopic((AgentId) topicIds.next(), -1, -1);

    // Deleting the temporary destinations:
    AgentId destId;
    for (Iterator dests = activeCtx.getTempDestinations(); dests.hasNext();) {
      destId = (AgentId) dests.next();
      activeCtx.removeTemporaryDestination(destId);
      deleteTemporaryDestination(destId);

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "Deletes temporary" + " destination " + destId.toString());
    }

    // Saving the prepared transactions.
    Iterator xids = activeCtx.getTxIds();
    Xid xid;
    XACnxPrepare recoveredPrepare;
    XACnxPrepare prepare;
    while (xids.hasNext()) {
      if (recoveredTransactions == null)
        recoveredTransactions = new Hashtable();

      xid = (Xid) xids.next();

      recoveredPrepare = (XACnxPrepare) recoveredTransactions.get(xid);
      prepare = activeCtx.getTxPrepare(xid);

      if (recoveredPrepare == null)
        recoveredTransactions.put(xid, prepare);
      else {
        recoveredPrepare.getSendings().addAll(prepare.getSendings());
        recoveredPrepare.getAcks().addAll(prepare.getAcks());
      }
    }

    // Finally, deleting the context:
    contexts.remove(new Integer(key));
    activeCtx = null;
    setActiveCtxId(-1);

    CnxCloseReply reply = new CnxCloseReply();
    reply.setCorrelationId(req.getRequestId());
    sendToClient(key, reply);
  }

  private void doReact(int key, ActivateConsumerRequest req) {
    String subName = req.getTarget();
    ClientSubscription sub = (ClientSubscription) subsTable.get(subName);
    sub.setActive(req.getActivate());
  }
 
  private void doReact(int key, CommitRequest req) {
    // The commit may involve some local agents
    int asyncReplyCount = 0;
   
    Enumeration pms = req.getProducerMessages();
    if (pms != null) {
      while (pms.hasMoreElements()) {
        ProducerMessages pm = (ProducerMessages) pms.nextElement();
        AgentId destId = AgentId.fromString(pm.getTarget());
        ClientMessages not = new ClientMessages(key,
            req.getRequestId(), pm.getMessages());
        setDmq(not);   
        if (destId.getTo() == getId().getTo()) {
          // local sending
          not.setPersistent(false);
          if (req.getAsyncSend()) {
            not.setAsyncSend(true);
          } else {
            asyncReplyCount++;
          }
        }
        sendNot(destId, not);
      }
    }
   
    Enumeration acks = req.getAckRequests();
    if (acks != null) {
      while (acks.hasMoreElements()) {
        SessAckRequest sar = (SessAckRequest) acks.nextElement();
        if (sar.getQueueMode()) {
          AgentId qId = AgentId.fromString(sar.getTarget());
          Vector ids = sar.getIds();
          AcknowledgeRequest not = new AcknowledgeRequest(activeCtxId, req
              .getRequestId(), ids);
          if (qId.getTo() == getId().getTo()) {
            // local sending
            not.setPersistent(false);
            // No reply to wait for
          }

          sendNot(qId, not);
        } else {
          String subName = sar.getTarget();
          ClientSubscription sub = (ClientSubscription) subsTable.get(subName);
          if (sub != null) {
            sub.acknowledge(sar.getIds().iterator());
            setSave();
          }
        }
      }
    }
  
    if (!req.getAsyncSend()) {
      if (asyncReplyCount == 0) {
        sendNot(getId(), new SendReplyNot(key, req
            .getRequestId()));
      } else {
        // we need to wait for the replies
        // from the local agents
        // before replying to the client.
        activeCtx.addMultiReplyContext(req.getRequestId(), asyncReplyCount);
      }
    }
    // else the client doesn't expect any ack
  }

  /**
   * Distributes the JMS replies to the appropriate reactions.
   * <p>
   * JMS proxies react the following replies:
   * <ul>
   * <li><code>QueueMsgReply</code></li>
   * <li><code>BrowseReply</code></li>
   * <li><code>SubscribeReply</code></li>
   * <li><code>TopicMsgsReply</code></li>
   * <li><code>ExceptionReply</code></li>
   * </ul>
   */
  private void doFwd(AgentId from, AbstractReplyNot rep) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG,
          "--- " + this + " got " + rep.getClass().getName() + " with id: " + rep.getCorrelationId()
              + " from: " + from);

    if (rep instanceof QueueMsgReply)
      doFwd(from, (QueueMsgReply) rep);
    else if (rep instanceof BrowseReply)
      doFwd((BrowseReply) rep);
    else if (rep instanceof SubscribeReply)
      doFwd((SubscribeReply) rep);
    else if (rep instanceof TopicMsgsReply)
      doFwd(from, (TopicMsgsReply) rep);
    else if (rep instanceof ExceptionReply)
      doReact(from, (ExceptionReply) rep);
    else {
      if (logger.isLoggable(BasicLevel.ERROR))
        logger.log(BasicLevel.ERROR, "Unexpected reply: " + rep);
    }
  }

  /**
   * Actually forwards a <code>QueueMsgReply</code> coming from a destination
   * as a <code>ConsumerMessages</code> destinated to the requesting client.
   * <p>
   * If the corresponding context is stopped, stores the
   * <code>ConsumerMessages</code> for later delivery.
   */
  private void doFwd(AgentId from, QueueMsgReply rep) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.doFwd(" + from + ',' + rep + ')');

    try {
      // Updating the active context:
      setCtx(rep.getClientContext());

      // If the receive request being replied has been cancelled, denying
      // the message.
      if (rep.getCorrelationId() == activeCtx.getCancelledReceive()) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> cancelled receive: id=" + activeCtx.getCancelledReceive());

        if (rep.getSize() > 0) {
          Vector msgList = rep.getMessages();
          for (int i = 0; i < msgList.size(); i++) {
            Message msg = new Message((org.objectweb.joram.shared.messages.Message) msgList.elementAt(i));
            String msgId = msg.getIdentifier();

            if (logger.isLoggable(BasicLevel.INFO))
              logger.log(BasicLevel.INFO, " -> denying message: " + msgId);

            sendNot(from, new DenyRequest(0, rep.getCorrelationId(), msgId));
          }
        }
      } else {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> reply");

        ConsumerMessages jRep;

        // Building the reply and storing the wrapped message id for later
        // denying in the case of a failure:
        if (rep.getSize() > 0) {
          jRep = new ConsumerMessages(rep.getCorrelationId(), rep.getMessages(), from.toString(), true);
          activeCtx.addDeliveringQueue(from);
        } else {
          jRep = new ConsumerMessages(rep.getCorrelationId(), (Vector) null, from.toString(), true);
        }

        // If the context is started, delivering the message, or buffering it:
        if (activeCtx.getActivated()) {
          doReply(jRep);
        } else {
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, " -> buffer the reply");
          activeCtx.addPendingDelivery(jRep);
        }
      }
    } catch (StateException pE) {
      // The context is lost: denying the message:
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "", pE);
      if (rep.getMessages().size() > 0) {
        Vector msgList = rep.getMessages();
        for (int i = 0; i < msgList.size(); i++) {
          Message msg = new Message((org.objectweb.joram.shared.messages.Message) msgList.elementAt(i));
          String msgId = msg.getIdentifier();

          if (logger.isLoggable(BasicLevel.INFO))
            logger.log(BasicLevel.INFO, "Denying message: " + msgId);

          sendNot(from, new DenyRequest(0, rep.getCorrelationId(), msgId));
        }
      }
    }
  }


  /**
   * Actually forwards a <code>BrowseReply</code> coming from a
   * destination as a <code>QBrowseReply</code> destinated to the
   * requesting client.
   */
  private void doFwd(BrowseReply rep) {
    try {
      // Updating the active context:
      setCtx(rep.getClientContext());
      doReply(new QBrowseReply(rep.getCorrelationId(),
                               rep.getMessages()));
    } catch (StateException pE) {
      // The context is lost; nothing to do.
    }
  }

  /**
   * Forwards the topic's <code>SubscribeReply</code> as a
   * <code>ServerReply</code>.
   */
  private void doFwd(SubscribeReply rep) {
    try {
      setCtx(rep.getClientContext());
      doReply(new ServerReply(rep.getCorrelationId()));
    } catch (StateException pE) {
      // The context is lost; nothing to do.
    }
  }

  transient String msgTxname = null;

  protected final String getMsgTxname() {
    if (msgTxname == null)
      msgTxname = 'M' + getId().toString() + '_';
    return msgTxname;
  }

  protected final void setMsgTxName(Message msg) {
    if (msg.getTxName() == null)
      msg.setTxName(getMsgTxname() + msg.order);
  }

  /**
   * Method implementing the proxy reaction to a <code>TopicMsgsReply</code>
   * holding messages published by a topic.
   */
  private void doFwd(AgentId from, TopicMsgsReply rep) {
    // Browsing the target subscriptions:
    TopicSubscription tSub = (TopicSubscription) topicsTable.get(from);
    if (tSub == null || tSub.isEmpty()) return;

    String subName;
    ClientSubscription sub;

    // AF: TODO we should parse each message for each subscription
    // see ClientSubscription.browseNewMessages
    List messages = new ArrayList();
    for (Iterator msgs = rep.getMessages().iterator(); msgs.hasNext();) {
      Message message = new Message((org.objectweb.joram.shared.messages.Message) msgs.next());
      // Setting the arrival order of the messages
      message.order = arrivalsCounter++;
      messages.add(message);
    }

    for (Iterator names = tSub.getNames(); names.hasNext();) {
      subName = (String) names.next();
      sub = (ClientSubscription) subsTable.get(subName);
      if (sub == null) continue;

      // Browsing the delivered messages.
      sub.browseNewMessages(messages);
    }

    // Save message if it is delivered to a durable subscription.
    for (Iterator msgs = messages.iterator(); msgs.hasNext();) {
      Message message = (Message) msgs.next();

      if (message.durableAcksCounter > 0) {
        if (logger.isLoggable(BasicLevel.DEBUG))
          logger.log(BasicLevel.DEBUG, " -> save message " + message);
        setSave();
        // Persisting the message.
        setMsgTxName(message);
        message.save();
        message.releaseFullMessage();
      }
    }

    for (Iterator names = tSub.getNames(); names.hasNext();) {
      subName = (String) names.next();
      sub = (ClientSubscription) subsTable.get(subName);
      if (sub == null) continue;

      // If the subscription is active, launching a delivery sequence.
      if (sub.getActive()) {
        ConsumerMessages consM = sub.deliver();
       
        if (consM != null) {
          try {
            setCtx(sub.getContextId());
            if (activeCtx.getActivated())
              doReply(consM);
            else
              activeCtx.addPendingDelivery(consM);
          } catch (StateException pE) {
            // The context is lost: nothing to do.
          }
        }
      }
    }
  }

  /**
   * Actually forwards an <code>ExceptionReply</code> coming from a destination
   * as a <code>MomExceptionReply</code> destinated to the requesting client.
   * <p>
   * If the wrapped exception is an <code>AccessException</code> thrown by
   * a <code>Topic</code> as a reply to a <code>SubscribeRequest</code>,
   * removing the corresponding subscriptions.
   */
  private void doReact(AgentId from, ExceptionReply rep) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.doReact(" + from + ',' + rep + ')');
    MomException exc = rep.getException();

    // The exception comes from a topic refusing the access: deleting the subs.
    if (exc instanceof AccessException) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> topicsTable.remove(" + from + ')');
      TopicSubscription tSub = (TopicSubscription) topicsTable.remove(from);
      if (tSub != null) {
        String name;
        ClientSubscription sub;
        for (Iterator e = tSub.getNames(); e.hasNext();) {
          name = (String) e.next();
          sub = (ClientSubscription) subsTable.remove(name);
          try {
            MXWrapper.unregisterMBean(getSubMBeanName(name));
          } catch (Exception e1) {
            if (logger.isLoggable(BasicLevel.WARN))
              logger.log(BasicLevel.WARN, "  - Problem when unregistering ClientSubscriptionMbean", e1);
          }
          sub.delete();

          try {
            setCtx(sub.getContextId());
            activeCtx.removeSubName(name);
            doReply(new MomExceptionReply(rep.getCorrelationId(), exc));
          } catch (StateException pExc) {}
        }
        return;
      }
    }
    // Forwarding the exception to the client.
    try {
      setCtx(rep.getClientContext());
      doReply(new MomExceptionReply(rep.getCorrelationId(), exc));
    } catch (StateException pExc) {}
  }
 
  private String getSubMBeanName(String name) {
    return getMBeanName().append(",sub=").append(name).toString();
  }

  /**
   * An <code>AdminReply</code> acknowledges the setting of a temporary
   * destination; nothing needs to be done.
   */
  private void doReact(AdminReplyNot reply) {}

  /**
   * Method implementing the JMS proxy reaction to an <code>UnknownAgent</code>
   * notification notifying that a destination does not exist or is deleted.
   * <p>
   * If it notifies of a deleted topic, the method removes the
   * corresponding subscriptions. If the wrapped request is messages sending,
   * the messages are sent to the DMQ.
   * <p>
   * A <code>JmsExceptReply</code> is sent to the concerned requester.
   * <p>
   * This case might also happen when sending a <code>ClientMessages</code>
   * to a dead message queue. In that case, the invalid DMQ identifier is set
   * to null.
   */
  private void doReact(UnknownAgent uA) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.doReact(" + uA + ')');
    Notification not = uA.not;
    AgentId agId = uA.agent;

    if (logger.isLoggable(BasicLevel.INFO))
      logger.log(BasicLevel.INFO, "--- " + this + " notified of invalid destination: " + agId.toString());

    // The deleted destination is a topic: deleting its subscriptions.
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, " -> topicsTable.remove(" + agId + ')');
    TopicSubscription tSub = (TopicSubscription) topicsTable.remove(agId);
    if (tSub != null) {
      String name;
      ClientSubscription sub;
      DestinationException exc;
      exc = new DestinationException("Destination " + agId + " does not exist.");
      for (Iterator e = tSub.getNames(); e.hasNext();) {
        name = (String) e.next();
        sub = (ClientSubscription) subsTable.remove(name);
        try {
          MXWrapper.unregisterMBean(getSubMBeanName(name));
        } catch (Exception e1) {
          if (logger.isLoggable(BasicLevel.WARN))
            logger.log(BasicLevel.WARN, "  - Problem when unregistering ClientSubscriptionMbean", e1);
        }
        sub.delete();

        try {
          setCtx(sub.getContextId());
          activeCtx.removeSubName(name);
          doReply(new MomExceptionReply(sub.getSubRequestId(), exc));
        } catch (StateException pExc) {}
      }
      return;
    }

    if (not instanceof AbstractRequestNot) {
      AbstractRequestNot req = (AbstractRequestNot) not;

      // If the wrapped request is messages sending,forwarding them to the DMQ:
      if (req instanceof ClientMessages) {
        // If the queue actually was a dead message queue, updating its
        // identifier:
        if (dmqId != null && agId.equals(dmqId)) {
          // state change, so save.
          setSave();
          dmqId = null;
          for (Iterator subs = subsTable.values().iterator(); subs.hasNext();)
            ((ClientSubscription) subs.next()).setDMQId(null);
        }
        // Sending the messages again if not coming from the default DMQ:
        if (Queue.getDefaultDMQId() != null && !agId.equals(Queue.getDefaultDMQId())) {
          DMQManager dmqManager = new DMQManager(dmqId, null);
          Iterator msgs = ((ClientMessages) req).getMessages().iterator();
          while (msgs.hasNext()) {
            org.objectweb.joram.shared.messages.Message msg = (org.objectweb.joram.shared.messages.Message) msgs.next();
            nbMsgsSentToDMQSinceCreation++;
            dmqManager.addDeadMessage(msg, MessageErrorConstants.DELETED_DEST);
          }
          dmqManager.sendToDMQ();
        }

        DestinationException exc;
        exc = new DestinationException("Destination " + agId + " does not exist.");
        MomExceptionReply mer = new MomExceptionReply(req.getRequestId(), exc);
        try {
          setCtx(req.getClientContext());
          // Contrary to a receive, send the error even if the
          // connection is not started.
          doReply(mer);
        } catch (StateException se) {
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "", se);         
          // Do nothing (the context doesn't exist any more).
        }
      } else if (req instanceof ReceiveRequest) {
        DestinationException exc = new DestinationException("Destination " + agId + " does not exist.");
        MomExceptionReply mer = new MomExceptionReply(req.getRequestId(), exc);
        try {
          setCtx(req.getClientContext());
          if (activeCtx.getActivated()) {
            doReply(mer);
          } else {
            activeCtx.addPendingDelivery(mer);
          }
        } catch (StateException se) {
          if (logger.isLoggable(BasicLevel.DEBUG))
            logger.log(BasicLevel.DEBUG, "", se);         
          // Do nothing (the contexte doesn't exist any more).
        }
      }
      if (logger.isLoggable(BasicLevel.INFO))
        logger.log(BasicLevel.INFO, "Connection " + req.getClientContext()
            + " notified of the deletion of destination " + agId);
    }
  }

  private void doReact(FwdAdminRequestNot not) {
    AdminRequest adminRequest = not.getRequest();
   
    if (adminRequest instanceof GetSubscriptions) {
      doReact((GetSubscriptions) adminRequest, not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetSubscriptionMessageIds) {
      doReact((GetSubscriptionMessageIds) adminRequest, not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetSubscriptionMessage) {
      doReact((GetSubscriptionMessage) adminRequest, not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof DeleteSubscriptionMessage) {
      doReact((DeleteSubscriptionMessage) adminRequest, not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetSubscription) {
      doReact((GetSubscription) adminRequest, not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof ClearSubscription) {
      doReact((ClearSubscription) adminRequest, not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetNbMaxMsgRequest) {
      GetNbMaxMsgRequest request = (GetNbMaxMsgRequest) adminRequest;
      int nbMaxMsg = -1;
      ClientSubscription sub = (ClientSubscription) subsTable.get(request.getSubName());
      if (sub != null)
        nbMaxMsg = sub.getNbMaxMsg();

      replyToTopic(new GetNumberReply(nbMaxMsg), not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof GetDMQSettingsRequest) {
      replyToTopic(new GetDMQSettingsReply((dmqId != null) ? dmqId.toString() : null, threshold),
          not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof SetDMQRequest) {
      setSave();
     
      if (((SetDMQRequest)adminRequest).getDmqId() != null)
        dmqId = AgentId.fromString(((SetDMQRequest)adminRequest).getDmqId());
      else
        dmqId = null;

      for (Iterator subs = subsTable.values().iterator(); subs.hasNext();)
        ((ClientSubscription) subs.next()).setDMQId(dmqId);

      replyToTopic(new AdminReply(true, null), not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof SetThresholdRequest) {
      setSave(); // state change, so save.
      threshold = ((SetThresholdRequest) adminRequest).getThreshold();
      for (Iterator subs = subsTable.values().iterator(); subs.hasNext();)
        ((ClientSubscription) subs.next()).setThreshold(threshold);

      replyToTopic(new AdminReply(true, null), not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof SetNbMaxMsgRequest) {
      setSave(); // state change, so save.
      int nbMaxMsg = ((SetNbMaxMsgRequest) adminRequest).getNbMaxMsg();
     
      AdminReply reply = null;
      String subName = ((SetNbMaxMsgRequest) adminRequest).getSubName();
      if (subName == null) {
        // Set the default subscription of this user
        this.nbMaxMsg = nbMaxMsg;
        reply =new AdminReply(true, null);
      } else {
        // Set the given subscription
        ClientSubscription sub = (ClientSubscription) subsTable.get(subName);
        if (sub != null) {
          sub.setNbMaxMsg(nbMaxMsg);
          reply = new AdminReply(true, null);
        } else {
          reply = new AdminReply(AdminReply.NAME_UNKNOWN, "Subscription unknow: " + subName);
        }
      }

      replyToTopic(reply, not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    } else if (adminRequest instanceof DeleteUser) {
      deleteProxy(not);
    } else if (adminRequest instanceof AdminCommandRequest) {
      doReact((AdminCommandRequest) adminRequest, not.getReplyTo(), not.getRequestMsgId());
    } else {
      logger.log(BasicLevel.ERROR, "Unknown administration request for proxy " + getId());
      replyToTopic(new AdminReply(AdminReply.UNKNOWN_REQUEST, null), not.getReplyTo(), not.getRequestMsgId(),
          not.getReplyMsgId());
     
    }
  }

  private void doReact(AdminCommandRequest request, AgentId replyTo, String requestMsgId) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "doReact(" + request + ", " + replyTo + ", " + requestMsgId + ')');
    Properties prop = null;
    Properties replyProp = null;
    try {
      switch (request.getCommand()) {
      case AdminCommandConstant.CMD_ADD_INTERCEPTORS:
        prop = request.getProp();
        if (interceptorsOUT == null)
          interceptorsOUT = new ArrayList();
        InterceptorsHelper.addInterceptors((String) prop.get(AdminCommandConstant.INTERCEPTORS_OUT), interceptorsOUT);
        interceptors_out = InterceptorsHelper.getListInterceptors(interceptorsOUT);
        if (interceptorsIN == null)
          interceptorsIN = new ArrayList();
        InterceptorsHelper.addInterceptors((String) prop.get(AdminCommandConstant.INTERCEPTORS_IN), interceptorsIN);
        interceptors_in = InterceptorsHelper.getListInterceptors(interceptorsIN);
        // state change
        setSave();
        break;
      case AdminCommandConstant.CMD_REMOVE_INTERCEPTORS:
        prop = request.getProp();
        InterceptorsHelper.removeInterceptors((String) prop.get(AdminCommandConstant.INTERCEPTORS_OUT), interceptorsOUT);
        interceptors_out = InterceptorsHelper.getListInterceptors(interceptorsOUT);
        InterceptorsHelper.removeInterceptors((String) prop.get(AdminCommandConstant.INTERCEPTORS_IN), interceptorsIN);
        interceptors_in = InterceptorsHelper.getListInterceptors(interceptorsIN);
        if (interceptorsIN != null && interceptorsIN.isEmpty())
          interceptorsIN = null;
        if (interceptorsOUT != null && interceptorsOUT.isEmpty())
          interceptorsOUT = null;
        // state change
        setSave();
        break;
      case AdminCommandConstant.CMD_GET_INTERCEPTORS:
        replyProp = new Properties();
        if (interceptors_in == null) {
                  replyProp.put(AdminCommandConstant.INTERCEPTORS_IN, "");
        } else {
                  replyProp.put(AdminCommandConstant.INTERCEPTORS_IN, InterceptorsHelper.getListInterceptors(interceptorsIN));
        }
        if (interceptors_out == null) {
                  replyProp.put(AdminCommandConstant.INTERCEPTORS_OUT, "");
        } else {
                  replyProp.put(AdminCommandConstant.INTERCEPTORS_OUT, InterceptorsHelper.getListInterceptors(interceptorsOUT));
        }
        break;
      case AdminCommandConstant.CMD_REPLACE_INTERCEPTORS:
        prop = request.getProp();
        if (interceptorsIN == null && prop.containsKey(AdminCommandConstant.INTERCEPTORS_IN_NEW))
          throw new Exception("interceptorsIN == null.");
        if (interceptorsOUT == null && prop.containsKey(AdminCommandConstant.INTERCEPTORS_OUT_NEW))
          throw new Exception("interceptorsOUT == null.");
        // replace IN interceptor
        InterceptorsHelper.replaceInterceptor(
            ((String) prop.get(AdminCommandConstant.INTERCEPTORS_IN_NEW)),
            ((String) prop.get(AdminCommandConstant.INTERCEPTORS_IN_OLD)),
            interceptorsIN);
        interceptors_in = InterceptorsHelper.getListInterceptors(interceptorsIN);
        // replace OUT interceptor
        InterceptorsHelper.replaceInterceptor(
            ((String) prop.get(AdminCommandConstant.INTERCEPTORS_OUT_NEW)),
            ((String) prop.get(AdminCommandConstant.INTERCEPTORS_OUT_OLD)),
            interceptorsOUT);
        interceptors_out = InterceptorsHelper.getListInterceptors(interceptorsOUT);
        // state change
        setSave();
        break;

      default:
        throw new Exception("Bad command : \"" + request.getCommand() + "\"");
      }
      // reply
      replyToTopic(new AdminCommandReply(true, AdminCommandConstant.commandNames[request.getCommand()] + " done.", replyProp), replyTo, requestMsgId, requestMsgId);
    } catch (Exception exc) {
      if (logger.isLoggable(BasicLevel.WARN))
        logger.log(BasicLevel.WARN, "", exc);
      replyToTopic(new AdminReply(-1, exc.getMessage()), replyTo, requestMsgId, requestMsgId);
    }
  }
 
  private void doReact(GetSubscriptions request, AgentId replyTo, String requestMsgId, String replyMsgId) {
    Iterator subsIterator = subsTable.entrySet().iterator();
    String[] subNames = new String[subsTable.size()];
    String[] topicIds = new String[subsTable.size()];
    int[] messageCounts = new int[subsTable.size()];
    boolean[] durable = new boolean[subsTable.size()];
    int i = 0;
    while (subsIterator.hasNext()) {
      Map.Entry subEntry = (Map.Entry) subsIterator.next();
      subNames[i] = (String) subEntry.getKey();
      ClientSubscription cs = (ClientSubscription) subEntry.getValue();
      topicIds[i] = cs.getTopicId().toString();
      messageCounts[i] = cs.getPendingMessageCount();
      durable[i] = cs.getDurable();
      i++;
    }
    GetSubscriptionsRep reply = new GetSubscriptionsRep(
      subNames, topicIds, messageCounts, durable);
    replyToTopic(reply, replyTo, requestMsgId, replyMsgId);
  }

  /**
   * Returns the list of subscriptions for this user. Each subscription is
   * identified by its unique 'symbolic' name.
   *
   * @return The list of subscriptions for this user.
   */
  public String[] getSubscriptionNames() {
    return (String[]) subsTable.keySet().toArray(new String[subsTable.size()]);
  }

  private void doReact(GetSubscriptionMessageIds request, AgentId replyTo, String requestMsgId,
      String replyMsgId) {
    String subName = request.getSubscriptionName();
    ClientSubscription cs = null;
    if (subName != null) {
      cs = (ClientSubscription) subsTable.get(subName);
    }
    if (cs != null) {
      GetSubscriptionMessageIdsRep reply = new GetSubscriptionMessageIdsRep(cs.getMessageIds());
      replyToTopic(reply, replyTo, requestMsgId, replyMsgId);
    } else {
      replyToTopic(new AdminReply(false, "Subscription not found: " + request.getSubscriptionName()),
          replyTo, requestMsgId, replyMsgId);
    }
  }

  private void doReact(GetSubscription request, AgentId replyTo, String requestMsgId, String replyMsgId) {
    String subName = request.getSubscriptionName();
    ClientSubscription cs = null;
    if (subName != null) {
      cs = (ClientSubscription) subsTable.get(subName);
    }
    if (cs != null) {
      GetSubscriptionRep reply = new GetSubscriptionRep(cs.getTopicId().toString(),
          cs.getPendingMessageCount(), cs.getDurable());
      replyToTopic(reply, replyTo, requestMsgId, replyMsgId);
    } else {
      replyToTopic(new org.objectweb.joram.shared.admin.AdminReply(false, "Subscription not found: "
          + request.getSubscriptionName()), replyTo, requestMsgId, replyMsgId);
    }
  }

  private void doReact(GetSubscriptionMessage request, AgentId replyTo, String requestMsgId, String replyMsgId) {
    ClientSubscription cs = null;
    String subName = request.getSubscriptionName();
    if (subName != null) {
      cs = (ClientSubscription)subsTable.get(subName);
    }
    if (cs != null) {
      String msgId = request.getMessageId();
     
      Message message = null;
      if (msgId != null)
        message = cs.getSubscriptionMessage(msgId);

      if (message != null) {
        GetSubscriptionMessageRep reply = null;
        if (request.getFullMessage()) {
          reply = new GetSubscriptionMessageRep(message.getFullMessage());
        } else {
          reply = new GetSubscriptionMessageRep(message.getHeaderMessage());
        }
        replyToTopic(reply, replyTo, requestMsgId, replyMsgId);
      } else {
        replyToTopic(new AdminReply(false, "Message not found: " + request.getMessageId()),
                     replyTo, requestMsgId, replyMsgId);
      }
    } else {
      replyToTopic(new AdminReply(false, "Subscription not found: " + subName),
                   replyTo, requestMsgId, replyMsgId);
    }
  }

  private void doReact(DeleteSubscriptionMessage request, AgentId replyTo, String requestMsgId,
      String replyMsgId) {
    String subName = request.getSubscriptionName();
    ClientSubscription cs = null;
    if (subName != null) {
      cs = (ClientSubscription) subsTable.get(subName);
    }
    if (cs != null) {
      cs.deleteMessage(request.getMessageId());
      replyToTopic(new AdminReply(true, null), replyTo, requestMsgId, replyMsgId);
    } else {
      replyToTopic(new AdminReply(false, "Subscription not found: " + request.getSubscriptionName()),
          replyTo, requestMsgId, replyMsgId);
    }
  }

  /**
   * Deletes a particular pending message in a subscription.
   * The subscription is identified  by its unique name, the message is pointed
   * out through its unique identifier.
   *
   * @param subName  The subscription unique name.
   * @param msgId    The unique message's identifier.
   */
  public void deleteSubscriptionMessage(String subName, String msgId) {
    ClientSubscription cs = (ClientSubscription) subsTable.get(subName);
    if (cs != null) {
      cs.deleteMessage(msgId);
    }
  }
 
  private void doReact(ClearSubscription request, AgentId replyTo, String requestMsgId, String replyMsgId) {
    String subName = request.getSubscriptionName();
    ClientSubscription cs = null;
    if (subName != null) {
      cs = (ClientSubscription) subsTable.get(subName);
    }
    if (cs != null) {
      cs.clear();
      replyToTopic(new AdminReply(true, null), replyTo, requestMsgId, replyMsgId);
    } else {
      replyToTopic(new AdminReply(false, "Subscription not found: " + request.getSubscriptionName()),
          replyTo, requestMsgId, replyMsgId);
    }
  }

  private void replyToTopic(AdminReply reply, AgentId replyTo, String requestMsgId, String replyMsgId) {
    org.objectweb.joram.shared.messages.Message message = MessageHelper.createMessage(replyMsgId,
        requestMsgId, replyTo.toString(), DestinationConstants.TOPIC_TYPE);
    try {
      message.setAdminMessage(reply);
      ClientMessages clientMessages = new ClientMessages(-1, -1, message);
      Channel.sendTo(replyTo, clientMessages);
    } catch (Exception exc) {
      if (logger.isLoggable(BasicLevel.ERROR))
        logger.log(BasicLevel.ERROR, "", exc);
      throw new Error(exc.getMessage());
    }
  }

  /**
   * Updates the reference to the active context.
   *
   * @param key  Key of the activated context.
   *
   * @exception StateException  If the context has actually been closed or
   *              lost.
   */
  private void setCtx(int key) throws StateException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.setCtx(" + key + ')');

    if (key < 0) throw new StateException("Invalid context: " + key);

    // If the required context is the last used, no need to update the
    // references:
    if (key == activeCtxId) return;

    // Else, updating the activeCtx reference:
    setActiveCtxId(key);
    activeCtx = (ClientContext) contexts.get(new Integer(key));

    // If context not found, throwing an exception:
    if (activeCtx == null) {
      setActiveCtxId(-1);
      activeCtx = null;
      throw new StateException("Context " + key + " is closed or broken.");
    }
  }
  /**
   * Method used for sending an <code>AbstractJmsReply</code> back to an
   * external client within the active context.
   *
   * @param rep  The reply to send.
   */
  private void doReply(AbstractJmsReply reply) {
    sendToClient(activeCtxId, reply);
  }

  protected ClientContext getClientContext(int ctxId) {
    return (ClientContext)contexts.get(
      new Integer(ctxId));
  }

  protected void cleanPendingMessages(long currentTime) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.cleanPendingMessages(" + messagesTable.size() + ')');
   
    Message message = null;
    DMQManager dmqManager = null;

    for (Iterator values = messagesTable.values().iterator(); values.hasNext();) {
      message = (Message) values.next();
      if ((message == null) || message.isValid(currentTime))
        continue;

      values.remove();
      if (message.durableAcksCounter > 0)
        message.delete();

      if (dmqManager == null)
        dmqManager = new DMQManager(dmqId, null);
      nbMsgsSentToDMQSinceCreation++;
      dmqManager.addDeadMessage(message.getFullMessage(), MessageErrorConstants.EXPIRED);

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, "UserAgent expired message " + message.getIdentifier());
    }
   
    Iterator subs = subsTable.values().iterator();
    while (subs.hasNext()) {
      ((ClientSubscription) subs.next()).cleanMessageIds();
    }
   
    // If needed, sending the dead messages to the DMQ:
    if (dmqManager != null)
      dmqManager.sendToDMQ();

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.cleanPendingMessages -> " + messagesTable.size());
  }

  public void delete() {
    DeleteUser request = new DeleteUser(getName(), getId().toString());
    FwdAdminRequestNot deleteNot = new FwdAdminRequestNot(request, null, null);
    Channel.sendTo(AdminTopic.getDefault(), deleteNot);
  }

  /**
   * This method deletes the proxy by notifying its connected clients,
   * denying the non acknowledged messages, deleting the temporary
   * destinations, removing the subscriptions.
   *
   * @exception Exception  If the requester is not an administrator.
   */
  private void deleteProxy(FwdAdminRequestNot not) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "--- " + this + " notified to be deleted.");

    String userName = ((DeleteUser) not.getRequest()).getUserName();
    if (contexts.size() > 0) {
      String info = "Delete proxy request successful [false]: proxy [" + getId() + "] of user ["
          + userName + "] is currently in use.";

      if (not.getReplyTo() != null) {
        replyToTopic(new AdminReply(AdminReply.PERMISSION_DENIED, info), not.getReplyTo(),
            not.getRequestMsgId(), not.getReplyMsgId());
      }
      return;
    }

    AdminTopic.deleteUser(userName);

    String info = "Delete proxy request successful [true]: proxy [" + getId() + "] of user ["
        + userName + "] has been notified of deletion";

    if (not.getReplyTo() != null) {
      replyToTopic(new AdminReply(true, info), not.getReplyTo(), not.getRequestMsgId(), not.getReplyMsgId());
    }

    // Removing all proxy's subscriptions:
    AgentId destId;
    for (Iterator topics = topicsTable.keySet().iterator(); topics.hasNext();) {
      destId = (AgentId) topics.next();
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> topicsTable.remove(" + destId + ')');
      topics.remove();
      updateSubscriptionToTopic(destId, -1, -1);
    }
   
    // Delete all subscriptions
    for (Iterator subs = subsTable.entrySet().iterator(); subs.hasNext();) {
      Map.Entry subEntry = (Entry) subs.next();
      String subName = (String) subEntry.getKey();
      ClientSubscription sub = (ClientSubscription) subEntry.getValue();

      // Deleting the subscription.
      sub.delete();
      try {
        MXWrapper.unregisterMBean(getSubMBeanName(subName));
      } catch (Exception e) {
        if (logger.isLoggable(BasicLevel.WARN))
          logger.log(BasicLevel.WARN, "  - Problem when unregistering ClientSubscriptionMbean", e);
      }
    }

    Channel.sendTo(getId(), new DeleteNot());

  }

  /**
   * Updates the proxy's subscription to a topic.
   *
   * @param topicId  Identifier of the topic to subscribe to.
   * @param contextId  Identifier of the subscription context.
   * @param requestId  Identifier of the subscription request.
   *
   * @return  <code>true</code> if a <code>SubscribeRequest</code> has been
   *          sent to the topic.
   */
  private boolean updateSubscriptionToTopic(AgentId topicId,
      int contextId,
      int requestId) {
    return updateSubscriptionToTopic(topicId, contextId, requestId, false);
  }
 
  /**
   * Updates the proxy's subscription to a topic.
   *
   * @param topicId  Identifier of the topic to subscribe to.
   * @param contextId  Identifier of the subscription context.
   * @param requestId  Identifier of the subscription request.
   * @param asyncSub   asynchronous subscription request.
   *
   * @return  <code>true</code> if a <code>SubscribeRequest</code> has been
   *          sent to the topic.
   */
  private boolean updateSubscriptionToTopic(AgentId topicId, int contextId, int requestId, boolean asyncSub) {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent.updateSubscriptionToTopic(" + topicId + ',' + contextId + ','
          + requestId + ',' + asyncSub + ')');

    TopicSubscription tSub = (TopicSubscription) topicsTable.get(topicId);

    // No more subs to this topic: unsubscribing.
    if (tSub == null || tSub.isEmpty()) {
      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> topicsTable.remove(" + topicId + ')');
      topicsTable.remove(topicId);
      sendNot(topicId,
                         new UnsubscribeRequest(contextId, requestId));
      return false;
    }

    // Otherwise, updating the subscription if the selector evolved.
    String builtSelector = tSub.buildSelector();
    if (tSub.getLastSelector() != null
        && builtSelector.equals(tSub.getLastSelector()))
      return false;

    tSub.setLastSelector(builtSelector);
    SubscribeRequest req = new SubscribeRequest(contextId, requestId, builtSelector, asyncSub);
    sendNot(topicId, req);
   
    // send reply if asynchronous subscription request.
    if (asyncSub) {
      doFwd(new SubscribeReply(req));
    }
   
    return true;
  }


  public void readBag(ObjectInputStream in) throws IOException, ClassNotFoundException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent[" + getId() + "].readbag()");

    connections = (Hashtable) in.readObject();
    heartBeatTasks = (Hashtable) in.readObject();

    if ((heartBeatTasks != null) && (heartBeatTasks.size() > 0)) {
      // Start all tasks
      Enumeration tasks = heartBeatTasks.elements();
      while (tasks.hasMoreElements())
        ((HeartBeatTask) tasks.nextElement()).start();
    }

    activeCtxId = in.readInt();
    /* // Orders elements is unknown, not use read bag in the same order
       Enumeration elements = contexts.elements();
       while (elements.hasMoreElements()) {
          ClientContext cc = (ClientContext)elements.nextElement();
          cc.setProxyAgent(proxyAgent);
          cc.readBag(in);
       }
       elements = subsTable.elements();
       while (elements.hasMoreElements()) {
          ClientSubscription cs = (ClientSubscription)elements.nextElement();
          cs.setProxyAgent(proxyAgent);
          cs.readBag(in);
       }*/
    /*** part modified */
    int size = in.readInt();
    Object obj = null;
    for (int j = 0; j < size; j++) {
      obj = in.readObject();
      ClientContext cc = (ClientContext) contexts.get(obj);
      cc.setProxyAgent(this);
      cc.readBag(in);
    }
    size = in.readInt();
    for (int j = 0; j < size; j++) {
      obj = in.readObject();
      ClientSubscription cs = (ClientSubscription) subsTable.get(obj);
      cs.setProxyAgent(this);
      cs.readBag(in);
    }
    /*** end part modified */ 
   
    activeCtx = (ClientContext)contexts.get(
      new Integer(activeCtxId));

    Vector messages = (Vector) in.readObject();

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, " -> messages = " + messages);
   
    topicsTable = new Hashtable();
    messagesTable = new Hashtable();

//    Vector topics = new Vector();
    TopicSubscription tSub;

    for (Iterator subsIterator = subsTable.entrySet().iterator(); subsIterator.hasNext();) {
      Map.Entry subEntry = (Map.Entry) subsIterator.next();
      String subName = (String) subEntry.getKey();

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> subName = " + subName);
     
      ClientSubscription cSub = (ClientSubscription) subEntry.getValue();
      AgentId destId = cSub.getTopicId();
//      if (! topics.contains(destId))
//        topics.add(destId);
      cSub.reinitialize(messagesTable, messages, false);

      if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> destId = " + destId + ')');
     
      tSub = (TopicSubscription) topicsTable.get(destId);
      if (tSub == null) {
        tSub = new TopicSubscription();
        topicsTable.put(destId, tSub);
      }
      tSub.putSubscription(subName, cSub.getSelector());
    }
    if (logger.isLoggable(BasicLevel.DEBUG))
        logger.log(BasicLevel.DEBUG, " -> topicsTable = " + topicsTable);

    // DF: seems not useful here
    // for (Enumeration topicIds = topics.elements();
//          topicIds.hasMoreElements();) {
//       updateSubscriptionToTopic((AgentId) topicIds.nextElement(), -1, -1);
//     }
  }

  public void writeBag(ObjectOutputStream out) throws IOException {
    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, "UserAgent[" + getId() + "].writeBag()");

    out.writeObject(connections);
    out.writeObject(heartBeatTasks);

    out.writeInt(activeCtxId);
   
    /*  Enumeration elements = contexts.elements();
  while (elements.hasMoreElements()) {
     ((ClientContext)elements.nextElement()).writeBag(out);
  }
  elements = subsTable.elements();
  while (elements.hasMoreElements()) {
     ((ClientSubscription)elements.nextElement()).writeBag(out);
  }*/

    /*** part modified */
    // the number of keys in contexts map
    out.writeInt(contexts.size());
    Iterator ctxs = contexts.entrySet().iterator();
    while (ctxs.hasNext()) {
      Map.Entry ctxEntry = (Map.Entry) ctxs.next();
      out.writeObject(ctxEntry.getKey());
      ((ClientContext) ctxEntry.getValue()).writeBag(out);
    }

    // the number of keys in subsTable map
    out.writeInt(subsTable.size());
    Iterator subs = subsTable.entrySet().iterator();
    while (subs.hasNext()) {
      Map.Entry subEntry = (Map.Entry) subs.next();
      out.writeObject(subEntry.getKey());
      ((ClientSubscription) subEntry.getValue()).writeBag(out);
    }
    /*** end part modified */

    List messages = new Vector();
    Iterator msgs = messagesTable.values().iterator();
    while (msgs.hasNext()) {
      messages.add(msgs.next());
    }

    if (logger.isLoggable(BasicLevel.DEBUG))
      logger.log(BasicLevel.DEBUG, " -> messages = " + messages + ')');

    out.writeObject(messages);
 
  }

  public long getNbMsgsSentToDMQSinceCreation() {
    return nbMsgsSentToDMQSinceCreation;
  }
}

/**
* The <code>Xid</code> internal class is a utility class representing
* a global transaction identifier.
*/
class Xid implements Serializable {
  /**
   *
   */
  private static final long serialVersionUID = 1L;
  byte[] bq;
  int fi;
  byte[] gti;

  Xid(byte[] bq, int fi, byte[] gti) {
    this.bq = bq;
    this.fi = fi;
    this.gti = gti;
  }

  public boolean equals(Object o) {
    if (!(o instanceof Xid))
      return false;

    Xid other = (Xid) o;

    return java.util.Arrays.equals(bq, other.bq)
           && fi == other.fi
           && java.util.Arrays.equals(gti, other.gti);
  }

  public int hashCode() {
    return (new String(bq) + "-" + new String(gti)).hashCode();
  }
}
TOP

Related Classes of org.objectweb.joram.mom.proxies.Xid

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
ss/fr/dyade/aaa/agent/WakeUpTask.html" title="Examples of fr.dyade.aaa.agent.WakeUpTask">fr.dyade.aaa.agent.WakeUpTask
  • org.objectweb.joram.mom.dest.AdminTopic
  • org.objectweb.joram.mom.dest.AdminTopic.DestinationDesc
  • org.objectweb.joram.mom.dest.Destination
  • org.objectweb.joram.mom.dest.Queue
  • org.objectweb.joram.mom.dest.Topic
  • TOP
    Copyright © 2018 www.massapi.com. All rights reserved.
    All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.