/*
 * JORAM: Java(TM) Open Reliable Asynchronous Messaging
 * Copyright (C) 2001 - 2020 ScalAgent Distributed Technologies
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA.
 *
 * Initial developer(s): ScalAgent Distributed Technologies
 */
package fr.dyade.aaa.jndi2.distributed;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.naming.CompositeName;
import javax.naming.NamingException;

import org.objectweb.util.monolog.api.BasicLevel;

import fr.dyade.aaa.agent.AgentId;
import fr.dyade.aaa.agent.AgentServer;
import fr.dyade.aaa.jndi2.impl.BindEvent;
import fr.dyade.aaa.jndi2.impl.ChangeOwnerEvent;
import fr.dyade.aaa.jndi2.impl.ContextRecord;
import fr.dyade.aaa.jndi2.impl.CreateSubcontextEvent;
import fr.dyade.aaa.jndi2.impl.DestroySubcontextEvent;
import fr.dyade.aaa.jndi2.impl.MissingContextException;
import fr.dyade.aaa.jndi2.impl.MissingRecordException;
import fr.dyade.aaa.jndi2.impl.NamingContext;
import fr.dyade.aaa.jndi2.impl.NamingContextInfo;
import fr.dyade.aaa.jndi2.impl.NotOwnerException;
import fr.dyade.aaa.jndi2.impl.RebindEvent;
import fr.dyade.aaa.jndi2.impl.Record;
import fr.dyade.aaa.jndi2.impl.UnbindEvent;
import fr.dyade.aaa.jndi2.impl.UpdateEvent;
import fr.dyade.aaa.jndi2.impl.UpdateListener;
import fr.dyade.aaa.jndi2.msg.ChangeOwnerRequest;
import fr.dyade.aaa.jndi2.msg.CreateSubcontextRequest;
import fr.dyade.aaa.jndi2.msg.JndiError;
import fr.dyade.aaa.jndi2.msg.JndiReply;
import fr.dyade.aaa.jndi2.msg.JndiRequest;
import fr.dyade.aaa.jndi2.server.JndiReplyNot;
import fr.dyade.aaa.jndi2.server.JndiScriptReplyNot;
import fr.dyade.aaa.jndi2.server.JndiScriptRequestNot;
import fr.dyade.aaa.jndi2.server.RequestContext;
import fr.dyade.aaa.jndi2.server.RequestManager;
import fr.dyade.aaa.jndi2.server.Trace;

public class ReplicationManager extends RequestManager implements UpdateListener {
  /** define serialVersionUID for interoperability */
  private static final long serialVersionUID = 1L;
  
  public final static String INIT_REQUEST_TABLE = "initRequestTable";
  public final static String SYNC_REQUEST_TABLE = "syncRequestTable";
  public final static String WRITE_REQUEST_TABLE = "writeRequestTable";
  public final static String SERVER_LIST = "serverList";

  /**
   * Identifier of the server that owns the root naming context.
   */
  private AgentId rootOwnerId;

  /**
   * List of the initially known servers.
   */
  private short[] serverIds;

  /**
   * List of the JNDI servers discovered by this server.
   * These servers first receive an initialization notification that contain the naming
   * data owned by this server. Then they receive the update notifications about the contexts
   * owned by this server.
   */
  private transient Vector servers;

  /**
   * Table that contains the write requests forwarded to the owner.
   * key = owner identifier (AgentId)
   * value = requests list (RequestContextList)
   */
  private transient Hashtable writeRequestContextLists;

  /**
   * Table that contains the requests (read or write) waiting for the initialization of the context.
   * key = id of the missing context
   * value = requests list (RequestContextList)
   */
  private transient Hashtable initRequestContextLists;

  /**
   * Table that contains the requests (read or write) waiting for the synchronization of the context.
   * key = owner identifier (AgentId)
   * value = requests list (RequestContextList)
   */
  private transient Hashtable syncRequestContextLists;

  private boolean looseCoupling;


  public ReplicationManager(short[] serverIds) {
    this.serverIds = serverIds;
  }

  /**
   * Overrides the <code>JndiServer</code> behavior.
   */
  protected AgentId getRootOwnerId() {
    return rootOwnerId;
  }

  public void agentInitialize(boolean firstTime) throws Exception {
    if (firstTime) {
      looseCoupling = AgentServer.getBoolean(fr.dyade.aaa.jndi2.impl.ServerImpl.LOOSE_COUPLING);
      if (serverIds.length > 0 && (!looseCoupling)) {
        rootOwnerId = DistributedJndiServer.getDefault(serverIds[0]);
      } else {
        rootOwnerId = getId();
      }
    }

    super.agentInitialize(firstTime);

    writeRequestContextLists = (Hashtable) AgentServer.getTransaction().load(WRITE_REQUEST_TABLE);
    if (writeRequestContextLists == null) {
      writeRequestContextLists = new Hashtable();
    }

    initRequestContextLists = (Hashtable) AgentServer.getTransaction().load(INIT_REQUEST_TABLE);
    if (initRequestContextLists == null) {
      initRequestContextLists = new Hashtable();
    }

    syncRequestContextLists = (Hashtable) AgentServer.getTransaction().load(SYNC_REQUEST_TABLE);
    if (syncRequestContextLists == null) {
      syncRequestContextLists = new Hashtable();
    }

    servers = (Vector)AgentServer.getTransaction().load(SERVER_LIST);
    if (servers == null) {
      servers = new Vector();      
      for (int i = 0; i < serverIds.length; i++) {        
        AgentId aid = DistributedJndiServer.getDefault(serverIds[i]);
        servers.addElement(aid);
        sendTo(aid, new InitJndiServerNot(null, null, true));
      }
      saveServers();
    }

    getServerImpl().setUpdateListener(this);
  }

  /**
   * Reacts to an update notification from an other JNDI server.
   * 
   * @param from Identifier of sender.
   * @param not Notification.
   * @throws Exception an error occurs during handling.
   */
  void doReact(AgentId from, JndiUpdateNot not) throws Exception {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(BasicLevel.DEBUG, 
                       "ReplicationManager[" + getId() + "].doReact(" +
                       from + ',' + not + ')');
    UpdateEvent updateEvent = not.getUpdateEvent();
    try {
      if (updateEvent instanceof BindEvent) {
        onUpdateEvent(from, (BindEvent)updateEvent);
      } else if (updateEvent instanceof RebindEvent) {
        onUpdateEvent(from, (RebindEvent)updateEvent);
      } else if (updateEvent instanceof UnbindEvent) {
        onUpdateEvent(from, (UnbindEvent)updateEvent);
      } else if (updateEvent instanceof CreateSubcontextEvent) {
        onUpdateEvent(from, (CreateSubcontextEvent)updateEvent);
      } else if (updateEvent instanceof DestroySubcontextEvent) {
        onUpdateEvent(from, (DestroySubcontextEvent)updateEvent);
      } else if (updateEvent instanceof ChangeOwnerEvent) {
        onUpdateEvent(from, (ChangeOwnerEvent)updateEvent);
      }
    } catch (NotOwnerException exc) {       
      // This may happen after a change owner event
      Trace.logger.log(BasicLevel.WARN,
                       "Distributed jndi update warn:",
                       exc);
    } catch (NamingException exc) { 
      if(!looseCoupling){
        Trace.logger.log(BasicLevel.ERROR, 
                         "Distributed jndi update error:",
                         exc);
        throw new Error(exc.toString());
      }
    }
  }  

  private void onUpdateEvent(AgentId from, BindEvent evt) 
  throws NamingException {
    if(!looseCoupling){
      getServerImpl().bind(
                           getServerImpl().getNamingContext(
                                                            evt.getUpdatedContextId()),
                                                            evt.getName(), 
                                                            evt.getObject(),
                                                            from);
    }else{
      NamingContext  nc= getServerImpl().getNamingContext(evt.getPath());
      getServerImpl().bind(
                           nc,
                           evt.getName(), 
                           evt.getObject(),
                           from);
    }

  }

  private void onUpdateEvent(AgentId from, RebindEvent evt) 
  throws NamingException {
    if(!looseCoupling){
      getServerImpl().rebind(
                             getServerImpl().getNamingContext(
                                                              evt.getUpdatedContextId()),
                                                              evt.getName(), 
                                                              evt.getObject(),
                                                              from);
    }else{
      NamingContext  nc= getServerImpl().getNamingContext(evt.getPath());
      getServerImpl().rebind(
                             nc,
                             evt.getName(), 
                             evt.getObject(),
                             from);
    }
  }

  private void onUpdateEvent(AgentId from, UnbindEvent evt) 
  throws NamingException {
    if(!looseCoupling){
      getServerImpl().unbind(
                             getServerImpl().getNamingContext(
                                                              evt.getUpdatedContextId()),
                                                              evt.getName(),
                                                              from);
    }else{
      NamingContext  nc= getServerImpl().getNamingContext(evt.getPath());
      getServerImpl().unbind(
                             nc,
                             evt.getName(),
                             from);
    }
  }

  private void onUpdateEvent(AgentId from, CreateSubcontextEvent evt) 
  throws NamingException {
    if(!looseCoupling){
      getServerImpl().createSubcontext(
                                       getServerImpl().getNamingContext(	
                                                                        evt.getUpdatedContextId()),
                                                                        evt.getName(),
                                                                        evt.getPath(),
                                                                        evt.getContextId(),
                                                                        evt.getOwnerId(),
                                                                        from);
    }else{
      CompositeName parentPath = (CompositeName) evt.getPath().clone();    
      parentPath.remove(parentPath.size() - 1);
      NamingContext  nc= getServerImpl().getNamingContext(parentPath);
      getServerImpl().createSubcontext(nc,
                                       evt.getName(),
                                       evt.getPath(),
                                       evt.getContextId(),
                                       getId(),
                                       from);
    }
  }

  private void onUpdateEvent(AgentId from, DestroySubcontextEvent evt) 
  throws NamingException {
    if(!looseCoupling){
      getServerImpl().destroySubcontext(
                                        getServerImpl().getNamingContext(
                                                                         evt.getUpdatedContextId()),
                                                                         evt.getName(), 
                                                                         evt.getPath(),
                                                                         from);
    }else{
      CompositeName parentPath = (CompositeName) evt.getPath().clone();    
      parentPath.remove(parentPath.size() - 1);
      NamingContext  nc= getServerImpl().getNamingContext(parentPath);
      getServerImpl().destroySubcontext(
                                        nc,
                                        evt.getName(), 
                                        evt.getPath(),
                                        from);

    }
  }

  private void onUpdateEvent(AgentId from, ChangeOwnerEvent evt) 
  throws NamingException {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(BasicLevel.DEBUG, 
                       "ReplicationManager.onUpdateEvent(" +
                       from + ',' + evt + ')');

    NamingContextInfo[] contexts = evt.getNamingContexts();
    for (int i = 0; i < contexts.length; i++) {
      NamingContext nc = getServerImpl().getNamingContext(
                                                          contexts[i].getNamingContext().getId());

      if (nc == null) {
        // The InitJndiServerNot sent by 
        // the server that created this context may not
        // have been received.
        getServerImpl().addNamingContext(contexts[i]);
        // TODO : NTA uncomment and implement retryRequestsWaitingForMissingContext
        //         retryRequestsWaitingForMissingContext(
        //           contexts[i].getNamingContext().getId());
      } else {
        nc.setOwnerId(contexts[i].getNamingContext().getOwnerId());
        getServerImpl().resetNamingContext(
                                           contexts[i].getNamingContext());
        // DF: must retry the sync and write
        // requests to the new owner.
      }
    }
  }  

  /**
   * Overrides the <code>JndiServer</code> behavior.
   * Send a JNDI request to the owner (JNDI server).
   * Waits for the asynchronous reply.
   * 
   * @param owner the owner.
   * @param reqCtx the request.
   * @return the reply.
   */
  protected JndiReply invokeOwner(AgentId owner,
                                  RequestContext reqCtx) {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(BasicLevel.DEBUG, "ReplicationManager.invokeOwner(" + owner + ',' + reqCtx + ')');
    JndiRequest request = reqCtx.getRequest();
    if (request instanceof CreateSubcontextRequest) {
      CreateSubcontextRequest csr = (CreateSubcontextRequest) request;
      request = new CreateRemoteSubcontextRequest(csr.getName(), getId());
    }

    sendTo(owner,
           new JndiScriptRequestNot(new JndiRequest[] { request }, true));
    RequestContextList list = (RequestContextList) writeRequestContextLists.get(owner);
    if (list == null) {
      list = new RequestContextList();
      writeRequestContextLists.put(owner, list);
    }
    list.put(reqCtx);
    saveWriteRequestTable();
    return null;
  }

  void doReact(AgentId from, JndiScriptReplyNot not) 
  throws Exception {
    onReply(from, not.getReplies()[0]);
  }

  void doReact(AgentId from, JndiReplyNot not) 
  throws Exception {
    onReply(from, not.getReply());
  }

  /**
   * Reacts to the reply of a JNDI server that has been called
   * as it is the owner of a naming context.
   * 
   * @param from Identifier of sender.
   * @param reply the reply.
   * @throws Exception an error occurs.
   */
  private void onReply(AgentId from, JndiReply reply) throws Exception {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(BasicLevel.DEBUG, 
                       "ReplicationManager[" + 
                       getId() + "].onReply(" +
                       from + ',' + reply + ')');
    RequestContextList ctxList = 
      (RequestContextList)writeRequestContextLists.get(from);
    RequestContext ctx = ctxList.get();
    ctxList.pop();
    if (ctxList.getSize() == 0) {
      writeRequestContextLists.remove(from);
    }    
    if (ctx != null) {
      ctx.reply(reply);
      saveWriteRequestTable();
    } else {
      Trace.logger.log(BasicLevel.ERROR,
                       "Reply context not found: " + 
                       from + ", " + reply);
    }
  }

  void doReact(AgentId from, 
               InitJndiServerNot not) throws Exception {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(BasicLevel.DEBUG, 
                       "ReplicationManager.doReact(" +
                       from + ',' + not + ')');

    if (servers == null)
      return;

    AgentId[] jndiServerIds = not.getJndiServerIds();
    Vector initServers = new Vector();
    if (jndiServerIds != null) {
      for (int i = 0; i < jndiServerIds.length; i++) {
        if (servers.indexOf(jndiServerIds[i]) < 0) {
          initServers.addElement(jndiServerIds[i]);
        }
      }
    }

    // Send back an init notif if:
    // - the init notif is a request
    // - or the server 'from' is unknown
    if (not.isRequest() || servers.indexOf(from) < 0) {
      initServers.addElement(from);
    }

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

    if (initServers.size() > 0) {
      AgentId[] localJndiServerIds = new AgentId[servers.size()];
      servers.copyInto(localJndiServerIds);
      NamingContextInfo[] localContexts = 
        getServerImpl().copyNamingContexts(getId());
      int serversInitialLength = servers.size();
      for (int i = 0; i < initServers.size(); i++) {
        AgentId newServerId = 
          (AgentId)initServers.elementAt(i);
        /** Modif */
        if (!(rootOwnerId.equals(initServers.elementAt(i))))
          sendTo(newServerId, new InitJndiServerNot(
                                                    localJndiServerIds, 
                                                    localContexts,
                                                    (!from.equals(newServerId))));
        if (servers.indexOf(newServerId) < 0) {
          servers.addElement(newServerId);
        }
        // else the server has already been registered.
        // (it is a recovering server)
      }
      if (servers.size() > serversInitialLength) {
        saveServers();
      }
    }

    NamingContextInfo[] contexts = not.getContexts();

    Hashtable  record_compositeName= new Hashtable();
    Hashtable  composite_context= new Hashtable();
    if (contexts != null) {
      Vector newNames = new Vector();
      for (int i = 0; i < contexts.length; i++) {
        if(!looseCoupling) {
          NamingContext nc = getServerImpl().getNamingContext(
                                                              contexts[i].getNamingContext().getId());
          if (nc == null) {
            getServerImpl().addNamingContext(contexts[i]);
            newNames.addElement(contexts[i].getCompositeName());
          }
          // Else the naming context has already been
          // added by an other server that is the (new)
          // owner of this context.

        } else {
          NamingContext nc;
          try{
            nc = getServerImpl().getNamingContext(contexts[i].getCompositeName());
          }catch(MissingRecordException mre){
            nc=null;
          }catch( MissingContextException mce){
            nc=null;
          }
          if(nc == null){
            nc = getServerImpl().newNamingContext( getId(),null,contexts[i].getCompositeName());
            contexts[i].getNamingContext().setOwnerId(getId());
          }

          Enumeration enumRecord = contexts[i].getNamingContext().getEnumRecord();
          while (enumRecord.hasMoreElements()) {          
            Record record =(Record)  enumRecord.nextElement();
            Record r = nc.getRecord(record.getName());
            if (r == null)  
              nc.addRecord(record);
            if(record instanceof ContextRecord){
              CompositeName parentPath = contexts[i].getCompositeName();
              record_compositeName.put(record,parentPath);
            }
          }
          composite_context.put(contexts[i].getCompositeName(),nc);
        }
      }
      if(looseCoupling){
        Enumeration enumKeyRecord = record_compositeName.keys();
        while(enumKeyRecord.hasMoreElements()) { 
          Record recor =(Record)enumKeyRecord.nextElement();
          CompositeName cn =(CompositeName)((CompositeName)(record_compositeName.get(recor))).clone();
          cn.add(recor.getName());
          if(composite_context.containsKey(cn)){
            NamingContext nc  =(NamingContext)composite_context.get(cn);
            ((ContextRecord)recor).setId(nc.getId());
          }	      
        }
        Enumeration enumContext = composite_context.elements();
        while(enumContext.hasMoreElements()) { 
          NamingContext nc  =(NamingContext)enumContext.nextElement();
          getServerImpl().storeNamingContext(nc);
        }
      }



      Vector retryNames = new Vector();
      Vector retryLists = new Vector();
      Enumeration names = initRequestContextLists.keys();
      Enumeration lists = initRequestContextLists.elements();
      while (lists.hasMoreElements()) {
        CompositeName name = 
          (CompositeName)names.nextElement();
        RequestContextList ctxList = 
          (RequestContextList)lists.nextElement();
        boolean retry = false;
        for (int i = 0; i < newNames.size(); i++) {
          CompositeName newName = 
            (CompositeName)newNames.elementAt(i);
          if (name.startsWith(newName)) {
            retry = true;
            break;
          }
        }
        if (retry) {
          retryNames.addElement(name);
          retryLists.addElement(ctxList);
        }
      }

      for (int i = 0; i < retryNames.size(); i++) {
        CompositeName name = 
          (CompositeName)retryNames.elementAt(i);
        RequestContextList ctxList = 
          (RequestContextList)retryLists.elementAt(i);
        initRequestContextLists.remove(name);
        while (ctxList.getSize() > 0) {
          RequestContext reqCtx = ctxList.get();
          JndiReply reply = invoke(reqCtx);
          if (reply != null) {
            reqCtx.reply(reply);
          }
          ctxList.pop();
        }
      }
      saveInitRequestTable();
    }
  }

  protected JndiReply onMissingContext(MissingContextException mce,
                                       RequestContext reqCtx) {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(BasicLevel.DEBUG, 
                       "ReplicationManager.onMissingContext(" +
                       mce + ',' + reqCtx + ')');
    RequestContextList ctxList = 
      (RequestContextList)initRequestContextLists.get(
                                                      mce.getName());
    if (ctxList == null) {
      ctxList = new RequestContextList();
      if (Trace.logger.isLoggable(BasicLevel.DEBUG))
        Trace.logger.log(BasicLevel.DEBUG, 
                         " -> add a waiting request context: " + 
                         mce.getName());
      initRequestContextLists.put(
                                  mce.getName(), ctxList);
    }
    ctxList.put(reqCtx);
    saveInitRequestTable();
    return null;
  }

  protected JndiReply onMissingRecord(MissingRecordException mre,
                                      RequestContext reqCtx) {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(BasicLevel.DEBUG, 
                       "ReplicationManager.onMissingRecord(" +
                       mre + ',' + reqCtx + ')');
    CompositeName resolvedName = 
      (CompositeName)mre.getNameNotFoundException().getResolvedName();
    if (mre.getOwnerId().equals(getId()) ||
        resolvedName.equals(reqCtx.getResolvedName())) {
      // The resolved context has already been updated.
      return new JndiError(mre.getNameNotFoundException());
    }

    reqCtx.setResolvedName(resolvedName);
    synchronizeRequest((AgentId)mre.getOwnerId(), reqCtx);
    return null;
  }

  private void synchronizeRequest(AgentId owner, 
                                  RequestContext reqCtx) {
    if (Trace.logger.isLoggable(BasicLevel.DEBUG))
      Trace.logger.log(
                       BasicLevel.DEBUG, 
                       "ReplicationManager.synchronizeRequest(" +
                       owner + ',' + reqCtx + ')');
    sendTo(owner, new SyncRequestNot());
    RequestContextList list = 
      (RequestContextList)syncRequestContextLists.get(owner);
    if (list == null) {
      list = new RequestContextList();
      syncRequestContextLists.put(owner, list);      
    }
    list.put(reqCtx);
    saveSyncRequestTable();
  }

  void doReact(AgentId from, SyncRequestNot not) {
    sendTo(from, new SyncReplyNot());
  }

  void doReact(AgentId from, SyncReplyNot not) {
    RequestContextList ctxList = (RequestContextList)syncRequestContextLists.get(from);
    RequestContext ctx = ctxList.get();
    ctxList.pop();
    if (ctxList.getSize() == 0) {
      syncRequestContextLists.remove(from);
    }    
    if (ctx != null) {
      JndiReply reply = invoke(ctx);
      if (reply != null) {
        ctx.reply(reply);
        saveSyncRequestTable();
      } 
    }
  }

  public void onUpdate(UpdateEvent event) {
    for (int i = 0; i < servers.size(); i++) {
      AgentId aid = (AgentId)servers.elementAt(i);
      sendTo(aid, new JndiUpdateNot(event));
    }
  }

  protected void createSubcontext(CreateSubcontextRequest request) 
  throws NamingException {
    if (request instanceof CreateRemoteSubcontextRequest) {
      createRemoteSubcontext((CreateRemoteSubcontextRequest)request);
    } else {
      super.createSubcontext(request);
    }
  }

  private void createRemoteSubcontext(CreateRemoteSubcontextRequest request)
  throws NamingException {
    getServerImpl().createSubcontext(
                                     request.getName(), request.getOwnerId());
  }

  protected void changeOwner(ChangeOwnerRequest request)
  throws NamingException {
    super.changeOwner(request);

    writeRequestContextLists.remove(request.getOwnerId());
    syncRequestContextLists.remove(request.getOwnerId());
    // DF: must reply to those requests because
    // this server is the new owner.
  }

  private void saveInitRequestTable() {
    try {
      AgentServer.getTransaction().save(
                                        initRequestContextLists, INIT_REQUEST_TABLE);
    } catch (IOException exc) {
      throw new Error(exc.toString());
    }
  }

  private void saveWriteRequestTable() {
    try {
      AgentServer.getTransaction().save(
                                        writeRequestContextLists, WRITE_REQUEST_TABLE);
    } catch (IOException exc) {
      throw new Error(exc.toString());
    }
  }

  private void saveSyncRequestTable() {
    try {
      AgentServer.getTransaction().save(
                                        syncRequestContextLists, SYNC_REQUEST_TABLE);
    } catch (IOException exc) {
      throw new Error(exc.toString());
    }
  }

  private void saveServers() {
    try {
      AgentServer.getTransaction().save(servers, SERVER_LIST);
    } catch (IOException exc) {
      throw new Error(exc.toString());
    }
  }

  static class RequestContextList implements java.io.Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private Vector list;

    RequestContextList() {
      this.list = new Vector();
    }

    void put(RequestContext ctx) {
      list.addElement(ctx);
    }

    RequestContext get() {
      if( list.size() > 0) return (RequestContext)list.elementAt(0);
      return null;
    }

    void pop() {
      if( list.size() > 0) {
        list.removeElementAt(0);
      }
    }

    int getSize() {
      return list.size();
    }

    public String toString() {
      return '(' + super.toString() + 
      ",list=" + list + ')';
    }
  }
}
