Package org.xmlBlaster.client

Source Code of org.xmlBlaster.client.XmlBlasterAccess

/*------------------------------------------------------------------------------
Name:      XmlBlasterAccess.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/
package org.xmlBlaster.client;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xmlBlaster.authentication.plugins.I_ClientPlugin;
import org.xmlBlaster.client.key.EraseKey;
import org.xmlBlaster.client.key.GetKey;
import org.xmlBlaster.client.key.PublishKey;
import org.xmlBlaster.client.key.SubscribeKey;
import org.xmlBlaster.client.key.UnSubscribeKey;
import org.xmlBlaster.client.key.UpdateKey;
import org.xmlBlaster.client.protocol.AbstractCallbackExtended;
import org.xmlBlaster.client.protocol.I_CallbackServer;
import org.xmlBlaster.client.qos.ConnectQos;
import org.xmlBlaster.client.qos.ConnectReturnQos;
import org.xmlBlaster.client.qos.DisconnectQos;
import org.xmlBlaster.client.qos.EraseQos;
import org.xmlBlaster.client.qos.EraseReturnQos;
import org.xmlBlaster.client.qos.GetQos;
import org.xmlBlaster.client.qos.PublishQos;
import org.xmlBlaster.client.qos.PublishReturnQos;
import org.xmlBlaster.client.qos.SubscribeQos;
import org.xmlBlaster.client.qos.SubscribeReturnQos;
import org.xmlBlaster.client.qos.UnSubscribeQos;
import org.xmlBlaster.client.qos.UnSubscribeReturnQos;
import org.xmlBlaster.client.qos.UpdateQos;
import org.xmlBlaster.client.queuemsg.MsgQueueConnectEntry;
import org.xmlBlaster.client.queuemsg.MsgQueueDisconnectEntry;
import org.xmlBlaster.client.queuemsg.MsgQueueEraseEntry;
import org.xmlBlaster.client.queuemsg.MsgQueueGetEntry;
import org.xmlBlaster.client.queuemsg.MsgQueuePublishEntry;
import org.xmlBlaster.client.queuemsg.MsgQueueSubscribeEntry;
import org.xmlBlaster.client.queuemsg.MsgQueueUnSubscribeEntry;
import org.xmlBlaster.jms.XBConnectionMetaData;
import org.xmlBlaster.util.FileDumper;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.I_ReplaceContent;
import org.xmlBlaster.util.I_Timeout;
import org.xmlBlaster.util.MsgUnit;
import org.xmlBlaster.util.SessionName;
import org.xmlBlaster.util.Timeout;
import org.xmlBlaster.util.Timestamp;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.admin.extern.JmxMBeanHandle;
import org.xmlBlaster.util.checkpoint.I_Checkpoint;
import org.xmlBlaster.util.cluster.NodeId;
import org.xmlBlaster.util.context.ContextNode;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.def.MethodName;
import org.xmlBlaster.util.dispatch.ConnectionStateEnum;
import org.xmlBlaster.util.dispatch.DispatchManager;
import org.xmlBlaster.util.dispatch.DispatchStatistic;
import org.xmlBlaster.util.dispatch.I_ConnectionStatusListener;
import org.xmlBlaster.util.dispatch.I_PostSendListener;
import org.xmlBlaster.util.error.I_MsgErrorHandler;
import org.xmlBlaster.util.key.MsgKeyData;
import org.xmlBlaster.util.qos.ClientProperty;
import org.xmlBlaster.util.qos.DisconnectQosData;
import org.xmlBlaster.util.qos.MsgQosData;
import org.xmlBlaster.util.qos.TopicProperty;
import org.xmlBlaster.util.qos.address.CallbackAddress;
import org.xmlBlaster.util.qos.storage.CbQueueProperty;
import org.xmlBlaster.util.qos.storage.ClientQueueProperty;
import org.xmlBlaster.util.qos.storage.HistoryQueueProperty;
import org.xmlBlaster.util.queue.I_Entry;
import org.xmlBlaster.util.queue.I_Queue;
import org.xmlBlaster.util.queue.StorageId;
import org.xmlBlaster.util.queuemsg.MsgQueueEntry;

/**
* This is the default implementation of the java client side remote access to xmlBlaster.
* <p>
* It hides a client side queue, the client side dispatcher framework for polling
* or pinging the server and some more features.
* </p>
* <p>
* The interface I_CallbackRaw/I_Callback/I_CallbackExtenden are enforced by AbstractCallbackExtended.
* </p>
*/
public /*final*/ class XmlBlasterAccess extends AbstractCallbackExtended
                   implements I_XmlBlasterAccess, I_ConnectionStatusListener, I_PostSendListener, XmlBlasterAccessMBean
{
   private static Logger log = Logger.getLogger(XmlBlasterAccess.class.getName());
   private String ME = "XmlBlasterAccess";
   private ContextNode contextNode;
   /**
    * The cluster node id (name) to which we want to connect, needed for nicer logging, typically null
    * Can be set manually from outside before connect
    */
   private String serverNodeId = null;
   private ConnectQos connectQos;
   /** The return from connect() */
   private ConnectReturnQos connectReturnQos;
   private long jmxPublicSessionId;
   /** Client side queue during connection failure */
   private I_Queue clientQueue;
   /** The dispatcher framework **/
   private DispatchManager dispatchManager;
   /** Statistic about send/received messages, can be null if there is a DispatchManager around */
   private volatile DispatchStatistic statistic;
   /** The object handling message delivery problems */
   private I_MsgErrorHandler msgErrorHandler;
   /** Client side helper classes to load the authentication xml string */
   private I_ClientPlugin secPlgn;
   /** The callback server */
   private I_CallbackServer cbServer;
   /** Handles the registered callback interfaces for given subscriptions. */
   private final UpdateDispatcher updateDispatcher;
   /** Used to callback the clients default update() method (as given on connect()) */
   private I_Callback updateListener;
   /** Is not null if the client wishes to be notified about connection state changes in fail safe operation */
   private I_ConnectionStateListener connectionListener;
   private I_PostSendListener postSendListener;
   /** Allow to cache updated messages for simulated synchronous access with get().
    * Do behind a get() a subscribe to allow cached synchronous get() access */
   private SynchronousCache synchronousCache;
   private boolean disconnectInProgress;
   private boolean connectInProgress;
   private String[] checkPointContext;

   /** this I_XmlBlasterAccess is valid until a 'leaveServer' invocation is done.*/
   private boolean isValid = true;

   private boolean firstWarn = true;

   private Timestamp sessionRefreshTimeoutHandle;
   /** My JMX registration */
   private JmxMBeanHandle mbeanHandle;
   /** First call to connect() in millis */
   private long startupTime;

   StreamingCallback streamingCb;

   private String storageIdPrefix;

   private FileDumper fileDumper;

   private boolean shutdown = false;
  
   private Object userObject;
  
   private XmlBlasterException toDeadXmlBlasterException;

   /**
    * Create an xmlBlaster accessor.
    * Please don't create directly but use the factory instead:
    * <pre>
    *   import org.xmlBlaster.util.Global;
    *   ...
    *   final Global glob = new Global(args);
    *   final I_XmlBlasterAccess xmlBlasterAccess = glob.getXmlBlasterAccess();
    * </pre>
    * @param glob Your environment handle or null to use the default Global.instance()
    *        You must use a cloned Global for each XmlBlasterAccess created.
    *        engine.Global is not allowed here, only util.Global is supported
    * @exception IllegalArgumentException If engine.Global is used as parameter
    */
   public XmlBlasterAccess(Global glob) {
      super((glob==null) ? Global.instance() : glob);
      //if (glob.wantsHelp()) {
      //   usage();
      //}
      if (super.glob.getNodeId() != null) {
         // it is a engine.Global!
         throw new IllegalArgumentException("XmlBlasterAccess can't be created with a engine.Global, please clone a org.xmlBlaster.util.Global to create me");
      }
      this.updateDispatcher = new UpdateDispatcher(super.glob);
   }

   /**
    * Create an xmlBlaster accessor.
    * Please don't create directly but use the factory instead:
    * <pre>
    *   final Global glob = new Global(args);
    *   final I_XmlBlasterAccess xmlBlasterAccess = glob.getXmlBlasterAccess();
    * </pre>
    * @param args Your command line arguments
    */
   public XmlBlasterAccess(String[] args) {
      super(new Global(args, true, false));
      this.updateDispatcher = new UpdateDispatcher(super.glob);
   }

   /**
    * @see org.xmlBlaster.client.I_XmlBlasterAccess#registerConnectionListener(I_ConnectionStateListener)
    */
   public synchronized void registerConnectionListener(I_ConnectionStateListener connectionListener) {
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Initializing registering connectionListener");
      this.connectionListener = connectionListener;
   }

   /**
    * Register a listener to get notifications when a messages is successfully send from
    * the client side tail back queue.
    * Max one can be registered, any old one will be overwritten
    * @param postSendListener The postSendListener to set.
    * @return the old listener or null if no previous was registered
    */
   public final I_PostSendListener registerPostSendListener(I_PostSendListener postSendListener) {
      I_PostSendListener old = this.postSendListener;
      this.postSendListener = postSendListener;
      return old;
   }

   /**
    * Called after a messages is send from the client side queue, but not for oneway messages.
    * Enforced by I_PostSendListener
    * @param msgQueueEntry, includes the returned QoS (e.g. PublisReturnQos)
    */
   public final void postSend(MsgQueueEntry[] entries) {
      for (int i=0; i<entries.length; i++) {
         MsgQueueEntry msgQueueEntry = entries[i];
         if (msgQueueEntry.getMethodName() == MethodName.CONNECT) {
            this.connectReturnQos = (ConnectReturnQos)msgQueueEntry.getReturnObj();
            if (this.connectReturnQos != null) {
               setContextNodeId(this.connectReturnQos.getServerInstanceId());
               // break; Loop to the latest if any
            }
            else {
               //log.warning("Expected connectReturnQos for " + msgQueueEntry.toXml() + " " + Global.getStackTraceAsString(null));
               if (log.isLoggable(Level.FINE)) log.fine("Expected connectReturnQos for " + msgQueueEntry.toXml() + " " + Global.getStackTraceAsString(null));
            }
         }
      }
      I_PostSendListener l = this.postSendListener;
      if (l != null) {
         try {
            l.postSend(entries);
         }
         catch (Throwable e) {
            e.printStackTrace();
         }
      }
   }
  
   public boolean sendingFailed(MsgQueueEntry[] entries, XmlBlasterException exception) {
      I_PostSendListener l = this.postSendListener;
      try {
         if (l == null) {
            for (int i=0; i<entries.length; i++) {
               MsgUnit msgUnit = entries[i].getMsgUnit();
               if (msgUnit != null) {
                  String fn = this.getFileDumper().dumpMessage(msgUnit.getKeyData(), msgUnit.getContent(),
                        msgUnit.getQosData());
                  log.severe("Async sending of message failed for message " + msgUnit.getKeyOid() + ", is dumped to "
                        + fn + ": " + exception.getMessage());
               } else {
                  log.severe("Async sending of message failed: " + entries[i].toXml() + ": " + exception.getMessage());
               }
            }
         }
         else {
            return l.sendingFailed(entries, exception);
         }
      }
      catch (Throwable e) {
         e.printStackTrace();
         for (int i=0; i<entries.length; i++)
            log.severe("Async sending of message failed for message " + entries[i].toXml() +"\nreason is: " + exception.getMessage());
      }
      return false;
   }
  
   public FileDumper getFileDumper() throws XmlBlasterException {
      if (this.fileDumper == null) {
         synchronized (this) {
            if (this.fileDumper == null) {
               this.fileDumper = new FileDumper(this.glob);
            }
         }
      }
      return this.fileDumper;
   }


   /**
    */
   public SynchronousCache createSynchronousCache(int size) {
      if (this.synchronousCache != null)
         return this.synchronousCache; // Is initialized already
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Initializing synchronous cache: size=" + size);
      this.synchronousCache = new SynchronousCache(glob, size);
      log.info(getLogId()+"SynchronousCache has been initialized with size="+size);
      return this.synchronousCache;
   }

   public void setClientErrorHandler(I_MsgErrorHandler msgErrorHandler) {
      this.msgErrorHandler = msgErrorHandler;
   }

   public String getConnectionQueueId() {
      if (this.clientQueue != null) {
         return this.clientQueue.getStorageId().toString();
      }
      return "";
   }

   /**
    * The unique name of this session instance.
    * @return Never null, for example "/xmlBlaster/node/heron/client/joe/session/-2"
    */
   public final ContextNode getContextNode() {
      return this.contextNode;
   }



   public ConnectReturnQos connect(ConnectQos qos, I_StreamingCallback streamingUpdateListener, boolean withQueue) throws XmlBlasterException {
      if (streamingUpdateListener == null)
         throw new XmlBlasterException(this.glob, ErrorCode.USER_ILLEGALARGUMENT, "connect", "the streamingUpdateListener is null, you must provide one");
      this.streamingCb = new StreamingCallback(this.glob, streamingUpdateListener, 0, 0, withQueue);
      if (withQueue)
         registerConnectionListener(this.streamingCb);
      return connect(qos, this.streamingCb);
   }
  
   /**
    * The storageId must remain the same after a client restart
    *
    * @param relating
    *           xbType like Constants.RELATING_CLIENT
    * @return
    */
   public StorageId createStorageId(String relating) {
      StorageId storageId = null;
      if (getStorageIdStr() != null && getStorageIdStr().length() > 0) {
         // client code forces a named client side storageId -
         // dangerous if the name conflicts with server name in same DB
         storageId = new StorageId(glob, serverNodeId, relating, getStorageIdStr());
      } else {
         if (getPublicSessionId() == 0) {
            // having no public sessionId we need to generate a unique
            // queue name
            storageId = new StorageId(glob, serverNodeId, relating, getId() + System.currentTimeMillis()
                  + Global.getCounter());
         } else {
            SessionName ses = getSessionName();
            if (ses != null)
               storageId = new StorageId(glob, serverNodeId, relating, ses);
            else
               storageId = new StorageId(glob, serverNodeId, relating, getId() + System.currentTimeMillis()
                     + Global.getCounter());
         }
      }
      return storageId;
   }

   /**
    * @see org.xmlBlaster.client.I_XmlBlasterAccess#connect(ConnectQos, I_Callback)
    */
   public ConnectReturnQos connect(ConnectQos qos, I_Callback updateListener) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "connect");

      synchronized (this) {

         if (this.startupTime == 0) {
            this.startupTime = System.currentTimeMillis();
         }

         if (isConnected() || this.connectInProgress) {
            String text = "connect() rejected, you are connected already, please check your code";
            throw new XmlBlasterException(glob, ErrorCode.USER_CONNECT_MULTIPLE, ME, text);
         }

         this.connectInProgress = true;

         try {
            this.connectQos = (qos==null) ? new ConnectQos(glob) : qos;

            ClientProperty tmp = this.connectQos.getClientProperty(Constants.UPDATE_BULK_ACK);
            if (tmp != null) {
               if (tmp.getBooleanValue()) {
                  log.info("Setting the flag '" + Constants.UPDATE_BULK_ACK + "' to 'true' since specified in ConnectQos");
                  this.updateBulkAck = true;
               }
            }


            // We need to set a unique ID for this client so that global.getId() is unique
            // which is used e.g. in the JDBC plugin
            SessionName sn = getSessionName();
            if (sn != null) {
               if (sn.isPubSessionIdUser()) {
                  this.glob.setId(sn.toString());
               }
               else {
                  this.glob.setId(sn.toString() + System.currentTimeMillis()); // Not secure if two clients start simultaneously
               }
            }
            else {
               this.glob.setId(getLoginName() + System.currentTimeMillis()); // Not secure if two clients start simultaneously
            }
            this.glob.resetInstanceId();
            this.connectQos.getData().setInstanceId(this.glob.getInstanceId());

            if (connectQos.getData().getGlobal().isServerSide()) {
               String text = "Your ConnectQos.getData() contains a ServerScope instead of a Global instance, this is not allowed";
               throw new XmlBlasterException(glob, ErrorCode.INTERNAL_ILLEGALARGUMENT, ME, text);
            }

            this.updateListener = updateListener;

            // TODO: This is done by ConnectQos already, isn't it?
            initSecuritySettings(this.connectQos.getData().getClientPluginType(),
                                 this.connectQos.getData().getClientPluginVersion());

            this.ME = "XmlBlasterAccess-" + getId();
            setContextNodeId(getServerNodeId());

            try {
               ClientQueueProperty prop = this.connectQos.getClientQueueProperty();
               StorageId storageId = createStorageId(Constants.RELATING_CLIENT);
               this.clientQueue = glob.getQueuePluginManager().getPlugin(prop.getType(), prop.getVersion(), storageId,
                                                      this.connectQos.getClientQueueProperty());
               if (this.clientQueue == null) {
                  String text = "The client queue plugin is not found with this configuration, please check your connect QoS: " + prop.toXml();
                  throw new XmlBlasterException(glob, ErrorCode.USER_CONFIGURATION, ME, text);
               }

               if (this.msgErrorHandler == null) {
                  this.msgErrorHandler = new ClientErrorHandler(glob, this);
               }

               boolean forceCbAddressCreation = (updateListener != null);
               this.dispatchManager = new DispatchManager(glob, this.msgErrorHandler,
                                       getSecurityPlugin(), this.clientQueue, this,
                                       this.connectQos.getAddresses(forceCbAddressCreation), sn);
               // the above can call toDead() and the client may have called shutdown(): this.connectQos == null again
               if (this.dispatchManager.isDead())
                   throw new XmlBlasterException(glob, ErrorCode.COMMUNICATION_NOCONNECTION_DEAD, ME, "connect call failed, your toDead() code did shutdown?");

               getDispatchStatistic(); // Force creation of dispatchStatistic as this syncs on 'this' and could deadlock if don later from a update()

               this.dispatchManager.getDispatchConnectionsHandler().registerPostSendListener(this);

               if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Switching to synchronous delivery mode ...");
               this.dispatchManager.trySyncMode(true);

               if (this.updateListener != null) { // Start a default callback server using same protocol
                  createDefaultCbServer();
               }

               if (this.connectQos.doSendConnect()) {
                  // Try to connect to xmlBlaster ...
                  sendConnectQos();
               }
               else {
                  log.info(getLogId()+"Initialized client library, but no connect() is send to xmlBlaster, a delegate should do any subscribe if required");
               }
            }
            catch (XmlBlasterException e) {
               if (isConnected()) disconnect((DisconnectQos)null);
               throw e;
            }
            catch (Throwable e) {
               if (isConnected()) disconnect((DisconnectQos)null);
               throw XmlBlasterException.convert(glob, ErrorCode.INTERNAL_UNKNOWN, ME, "Connection failed", e);
            }
         }
         finally {
            this.connectInProgress = false;
         }
      } // synchronized

      if (this.connectQos.getRefreshSession()) {
         startSessionRefresher();
      }

      if (isAlive()) {
         if (this.connectionListener != null) {
            this.connectionListener.reachedAlive(ConnectionStateEnum.UNDEF, this);
         }
         log.info(glob.getReleaseId() + ": Successful " + this.connectQos.getAddress().getType() + " login as " + getId());

         if (this.clientQueue.getNumOfEntries() > 0) {
            long num = this.clientQueue.getNumOfEntries();
            log.info(getLogId()+"We have " + num + " client side queued tail back messages");
            this.dispatchManager.switchToASyncMode();
            while (this.clientQueue.getNumOfEntries() > 0) {
               try { Thread.sleep(20L); } catch( InterruptedException i) {}
            }
            log.info((num-this.clientQueue.getNumOfEntries()) + " client side queued tail back messages sent");
            this.dispatchManager.switchToSyncMode();
         }
         else {
            if (this.connectionListener != null)
               this.connectionListener.reachedAliveSync(ConnectionStateEnum.ALIVE, this);
         }
      }
      else {
         if (this.connectionListener != null) {
            this.connectionListener.reachedPolling(ConnectionStateEnum.UNDEF, this);
         }
         log.info(glob.getReleaseId() + ": Login request as " + getId() + " is queued");
      }

      if (this.connectReturnQos != null) {
         setContextNodeId(this.connectReturnQos.getServerInstanceId());
      }

      return this.connectReturnQos; // new ConnectReturnQos(glob, "");
   }

   /**
    * Sends the current connectQos to xmlBlaster and stores the connectReturnQos.
    * @throws XmlBlasterException
    */
   private void sendConnectQos() throws XmlBlasterException {
      MsgQueueConnectEntry entry = new MsgQueueConnectEntry(this.glob, this.clientQueue.getStorageId(), this.connectQos.getData());
      // Try to connect to xmlBlaster ...
      this.connectReturnQos = (ConnectReturnQos)queueMessage(entry);
      this.connectReturnQos.getData().setInitialConnectionState(this.dispatchManager.getDispatchConnectionsHandler().getState());
   }

   public boolean isConnected() {
      if (this.dispatchManager != null) {
         return this.connectReturnQos != null && !this.dispatchManager.getDispatchConnectionsHandler().isDead();
      }
      return this.connectReturnQos != null;
   }

   private void startSessionRefresher() {
      if (this.connectQos == null) return;
      long sessionTimeout = this.connectQos.getSessionQos().getSessionTimeout();
      final long MIN = 2000L; // Sessions which live less than 2 seconds are not supported
      if (sessionTimeout >= MIN) {
         long gap = (sessionTimeout < 60*1000L) ? sessionTimeout/2 : sessionTimeout-30*1000L;
         final long refreshTimeout = sessionTimeout - gap;
         final Timeout timeout = this.glob.getPingTimer();
         this.sessionRefreshTimeoutHandle = timeout.addTimeoutListener(new I_Timeout() {
               public void timeout(Object userData) {
                  if (isAlive()) {
                     if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Refreshing session to not expire");
                     try {
                        refreshSession();
                     }
                     catch (XmlBlasterException e) {
                        log.warning(getLogId()+"Can't refresh the login session '" + getId() + "': " + e.toString());
                     }
                  }
                  else {
                     if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Can't refresh session as we have no connection");
                  }
                  try {
                     sessionRefreshTimeoutHandle = timeout.addOrRefreshTimeoutListener(this, refreshTimeout, null, sessionRefreshTimeoutHandle) ;
                  }
                  catch (XmlBlasterException e) {
                     log.warning(getLogId()+"Can't refresh the login session '" + getId() + "': " + e.toString());
                  }
               }
            },
            refreshTimeout, null);
      }
      else {
         log.warning(getLogId()+"Auto-refreshing session is not supported for session timeouts smaller " + MIN + " seconds");

      }
   }


   /**
    * @see I_XmlBlasterAccess#refreshSession()
    */
   public void refreshSession() throws XmlBlasterException {
      GetKey gk = new GetKey(glob, "__refresh");
      GetQos gq = new GetQos(glob);
      get(gk, gq);
   }

   /**
    * Extracts address data from ConnectQos (or adds default if missing)
    * and instantiate a callback server as specified in ConnectQos
    */
   private void createDefaultCbServer() throws XmlBlasterException {
      CbQueueProperty prop = connectQos.getSessionCbQueueProperty(); // Creates a default property for us if none is available
      CallbackAddress addr = prop.getCurrentCallbackAddress(); // may return null
      if (addr == null)
         addr = new CallbackAddress(glob);

      this.cbServer = initCbServer(getLoginName(), addr);

      addr.setType(this.cbServer.getCbProtocol());
      addr.setRawAddress(this.cbServer.getCbAddress());
      //addr.setVersion(this.cbServer.getVersion());
      //addr.setSecretSessionId(cbSessionId);
      prop.setCallbackAddress(addr);

      log.info(getLogId()+"Callback settings: " + prop.getSettings());
   }

   /**
    * @see I_XmlBlasterAccess#initCbServer(String, CallbackAddress)
    */
   public I_CallbackServer initCbServer(String loginName, CallbackAddress callbackAddress) throws XmlBlasterException {
      if (callbackAddress == null)
         callbackAddress = new CallbackAddress(glob);
      callbackAddress.setSessionName(this.getSessionName());
      if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Using 'client.cbProtocol=" + callbackAddress.getType() + "' to be used by " + getServerNodeId() + ", trying to create the callback server ...");
      I_CallbackServer server = glob.getCbServerPluginManager().getPlugin(callbackAddress.getType(), callbackAddress.getVersion());
      server.initialize(this.glob, loginName, callbackAddress, this);
      return server;
   }

   /**
    * Initializes the little client helper framework for authentication.
    * <p />
    * The first goal is a proper loginQoS xml string for authentication.
    * <p />
    * The second goal is to intercept the messages for encryption (or whatever the
    * plugin supports).
    * <p />
    * See xmlBlaster.properties, for example:
    * <pre>
    *   Security.Client.DefaultPlugin=gui,1.0
    *   Security.Client.Plugin[gui][1.0]=org.xmlBlaster.authentication.plugins.gui.ClientSecurityHelper
    * </pre>
    */
   private void initSecuritySettings(String secMechanism, String secVersion) {
      PluginLoader secPlgnMgr = glob.getClientSecurityPluginLoader();
      try {
         this.secPlgn = secPlgnMgr.getClientPlugin(secMechanism, secVersion);
         if (secMechanism != null// to avoid double logging for login()
            log.info(getLogId()+"Loaded security plugin=" + secMechanism + " version=" + secVersion);
      }
      catch (XmlBlasterException e) {
         log.severe(getLogId()+"Security plugin '" + secMechanism + "/" + secVersion +
                       "' initialization failed. Reason: "+e.getMessage());
         this.secPlgn = null;
      }
   }

   public I_ClientPlugin getSecurityPlugin() {
      return this.secPlgn;
   }

   /**
    * @see org.xmlBlaster.client.XmlBlasterAccessMBean#disconnect(String)
    */
   public String disconnect(String disconnectQos) {
     DisconnectQosData dqd = new DisconnectQosData(this.glob, null, disconnectQos);
     boolean success = disconnect(new DisconnectQos(this.glob, dqd));
     return "Disconnect called, success=" + success;
   }

   /**
    * @see org.xmlBlaster.client.I_XmlBlasterAccess#disconnect(DisconnectQos)
    * @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/interface.disconnect.html">interface.disconnect requirement</a>
    */
   public synchronized boolean disconnect(DisconnectQos disconnectQos) {
      if (!this.isValid) return false;
      // Relaxed check to allow shutdown of database without successful connection
      if (this.connectQos == null /*!isConnected()*/) {
         log.warning(getLogId()+"You called disconnect() but you are are not logged in, we ignore it.");
         if (glob != null)
           glob.shutdown();
         //shutdown(disconnectQos);
         return false;
      }

      if (disconnectQos == null)
         disconnectQos = new DisconnectQos(glob);

      if (!disconnectQos.getClearClientQueueProp().isModified()) {
         boolean clearClientQueue = true;
         if (this.connectQos != null) {
            if (this.connectQos.getSessionName().isPubSessionIdUser())
               clearClientQueue = false// Keep tail back messages
         }
         disconnectQos.clearClientQueue(clearClientQueue);
      }

      return shutdown(disconnectQos);
   }

   /**
    * @see org.xmlBlaster.client.XmlBlasterAccessMBean#leaveServer()
    */
   public String leaveServer() {
     leaveServer(null);
     return "Clientlibrary is shutdown";
   }

   /**
    * @see org.xmlBlaster.client.I_XmlBlasterAccess#leaveServer(Map)
    */
   public void leaveServer(Map map) {
      if (!this.isValid) return;
      synchronized(this) {
         this.isValid = false;
         DisconnectQos disconnectQos = new DisconnectQos(glob);
         disconnectQos.clearClientQueue(false);
         disconnectQos.clearSessions(false);
         disconnectQos.deleteSubjectQueue(false);
         disconnectQos.setLeaveServer(true);
         disconnectQos.shutdownCbServer(true);
         disconnectQos.shutdownDispatcher(true);
         shutdown(disconnectQos);
      }
   }

   private synchronized boolean shutdown(DisconnectQos disconnectQos) {
      if (this.disconnectInProgress) {
         log.warning(getLogId()+"Calling disconnect again is ignored, you are in shutdown progress already");
         return false;
      }

      this.disconnectInProgress = true;

      this.glob.unregisterMBean(this.mbeanHandle);

      if (disconnectQos == null)
         disconnectQos = new DisconnectQos(glob);

      if (isConnected()) {

         if (this.clientQueue != null) {
            long remainingEntries = this.clientQueue.getNumOfEntries();
            if (remainingEntries > 0) {
               if (disconnectQos.clearClientQueue())
                  log.warning(getLogId()+"You called disconnect(). Please note that there are " + remainingEntries +
                               " unsent invocations/messages in the queue which are discarded now.");
               else
                  log.info(getLogId()+"You called disconnect(). Please note that there are " + remainingEntries +
                               " unsent invocations/messages in the queue which are sent on next connect of the same client with the same public session ID.");
            }
         }

         if (!disconnectQos.isLeaveServer()) {
            String[] subscriptionIdArr = this.updateDispatcher.getSubscriptionIds();
            for (int ii=0; ii<subscriptionIdArr.length; ii++) {
               String subscriptionId = subscriptionIdArr[ii];
               UnSubscribeKey key = new UnSubscribeKey(glob, subscriptionId);
               try {
                  unSubscribe(key, null);
               }
               catch(XmlBlasterException e) {
                  if (e.isCommunication()) {
                     break;
                  }
                  log.warning(getLogId()+"Couldn't unsubscribe '" + subscriptionId + "' : " + e.getMessage());
               }
            }
         }

         // Now send the disconnect() to the server ...
         if (!disconnectQos.isLeaveServer() && this.clientQueue != null) {
            try {
               MsgQueueDisconnectEntry entry = new MsgQueueDisconnectEntry(this.glob, this.clientQueue.getStorageId(), disconnectQos);
               queueMessage(entry)// disconnects are always transient
               log.info(getLogId()+"Successful disconnect from " + getServerNodeId());
            } catch(Throwable e) {
               e.printStackTrace();
               log.warning(e.toString());
            }
         }
      }

      if (this.synchronousCache != null) {
         this.synchronousCache.clear();
      }

      if (this.clientQueue != null && disconnectQos.clearClientQueue()) {
         this.clientQueue.clear();
      }

      if (disconnectQos.shutdownDispatcher()) {
         if (this.dispatchManager != null) {
            this.dispatchManager.shutdown();
            //this.dispatchManager = null;
         }
         if (this.clientQueue != null) {
            this.clientQueue.shutdown(); // added to make hsqldb shutdown
            this.clientQueue = null;
         }
      }

      if (disconnectQos.shutdownCbServer() && this.cbServer != null) {
         try {
            this.cbServer.shutdown();
            this.cbServer = null;
         } catch (Throwable e) {
            e.printStackTrace();
            log.warning(e.toString());
         }
      }

      this.updateDispatcher.clear();

      if (this.secPlgn != null) {
         this.secPlgn = null;
      }

      this.connectQos = null;
      this.connectReturnQos = null;
      this.disconnectInProgress = false;
      this.msgErrorHandler = null;
      this.updateListener = null;

      this.streamingCb = null;

      super.glob.shutdown();

      this.shutdown = true;
      return true;
   }

   /**
    * @return true if shutdown was called, typically by disconnect()
    */
   public boolean isShutdown() {
     return this.shutdown;
   }

   /**
    * Access the callback server.
    * @return null if no callback server is established
    */
   public I_CallbackServer getCbServer() {
      return this.cbServer;
   }

   /**
    * Create a descriptive ME, for logging only
    * @return e.g. "/node/heron/client/joe/3" or "UNKNOWN_SESSION" if connect() was not successful
    */
   public String getId() {
      SessionName sessionName = getSessionName();
      return (sessionName == null) ? "UNKNOWN_SESSION" : sessionName.getAbsoluteName();
   }

   /**
    * Useful as a logging prefix.
    * @return For example "client/TheDesperate/-6: "
    */
   public String getLogId() {
      SessionName sessionName = getSessionName();
      return (sessionName == null) ? "" : sessionName.getRelativeName() + ": ";
   }

   /**
    * The public session ID of this login session.
    */
   public SessionName getSessionName() {
      if (this.connectReturnQos != null)
         return this.connectReturnQos.getSessionName();
      if (this.connectQos != null) {
         SessionName sessionName = this.connectQos.getSessionName();
         if (sessionName != null && sessionName.getNodeIdStr() == null && this.serverNodeId != null) {
            // In cluster setup the remote cluster node id is forced
            SessionName sn = new SessionName(glob, new NodeId(this.serverNodeId), sessionName.getLoginName(),
                  sessionName.getPublicSessionId());
            // log.info("Using sessionName=" + sn.getAbsoluteName());
            this.connectQos.setSessionName(sn);
            return sn;
         }
         return sessionName;
      }
      return null;
   }

   /**
    * @see I_XmlBlasterAccess#getStorageIdStr()
    */
   public String getStorageIdStr() {
      return this.storageIdPrefix;
   }

   /**
    * @see I_XmlBlasterAccess#setStorageIdStr(String)
    */
   public void setStorageIdStr(String prefix) {
      this.storageIdPrefix = Global.getStrippedString(prefix);
   }


   /**
    * Allows to set the node name for nicer logging.
    * Typically used by cluster clients and not by ordinary clients
    * @param serverNodeId For example "/node/heron/instanceId/1233435" or "/node/heron"
    */
   public void setServerNodeId(String nodeId) {
      if (nodeId == null) return;
      if (nodeId.startsWith("/node") || nodeId.startsWith("/xmlBlaster/node"))
         this.serverNodeId = nodeId;
      else
         this.serverNodeId = "/node/" + nodeId;
   }

   /**
    * The cluster node id (name) to which we want to connect.
    * <p />
    * Needed for client queue storage identifier. see: setStorageIdStr()
    * <p />
    * for nicer logging when running in a cluster.<br />
    * Is configurable with "-server.node.id golan" until a successful connect
    *
    * @return e.g. "/node/golan" or /xmlBlaster/node/heron"
    */
   public String getServerNodeId() {
      if (this.contextNode != null) return this.contextNode.getParent(ContextNode.CLUSTER_MARKER_TAG).getAbsoluteName();
      if (this.serverNodeId != null) return this.serverNodeId;
      return this.glob.getInstanceId(); // Changes for each restart
   }

   /**
    * Set my identity.
    * @param serverNodeId For example "/node/heron/instanceId/1233435" or "/node/heron"
    */
   private void setContextNodeId(String nodeId) {
      // Not for cluster with given serverNodeId: It is invariant
      if (this.serverNodeId != null)
         nodeId = this.serverNodeId;
     
      if (nodeId == null) return;
      if (nodeId.indexOf("/") == -1) nodeId = "/node/"+nodeId; // add CLUSTER_MARKER_TAG to e.g. "/node/avalon.mycomp.com"

      String oldClusterObjectName = "";      // e.g. "org.xmlBlaster:nodeClass=node,node=clientSUB1"
      String oldServerNodeInstanceName = ""; // e.g. "clientSUB1"
      ContextNode clusterContext = null;
      if (this.contextNode != null) {
         // same instance as glob.getContextNode():
         clusterContext = this.contextNode.getParent(ContextNode.CLUSTER_MARKER_TAG);
         oldServerNodeInstanceName = clusterContext.getInstanceName();
         oldClusterObjectName = clusterContext.getAbsoluteName(ContextNode.SCHEMA_JMX);
      }

      // Verify the publicSessionId ...
      try {
         if (this.mbeanHandle != null && this.jmxPublicSessionId != getPublicSessionId()) {
            /*int count = */this.glob.getJmxWrapper().renameMBean(this.mbeanHandle.getObjectInstance().getObjectName().toString(),
                           ContextNode.SESSION_MARKER_TAG, ""+getPublicSessionId());
            this.mbeanHandle.getContextNode().setInstanceName(""+getPublicSessionId());
            this.jmxPublicSessionId = getPublicSessionId();
         }
         if (this.mbeanHandle == null &&
             this.contextNode != null &&
             !this.contextNode.getInstanceName().equals(""+getPublicSessionId())) {
            this.contextNode.setInstanceName(""+getPublicSessionId());
         }
      }
      catch (XmlBlasterException e) {
         log.warning(getLogId()+"Ignoring problem during JMX session registration: " + e.toString());
      }

      // parse new cluster node name ...
      ContextNode tmp = ContextNode.valueOf(nodeId);
      ContextNode tmpClusterContext = (tmp==null)?null:tmp.getParent(ContextNode.CLUSTER_MARKER_TAG);
      if (tmpClusterContext == null) {
         log.severe(getLogId()+"Ignoring unknown serverNodeId '" + nodeId + "'");
         return;
      }
      String newServerNodeInstanceName = tmpClusterContext.getInstanceName(); // e.g. "heron"

      if (oldServerNodeInstanceName.equals(newServerNodeInstanceName)) {
         return; // nothing to do, same cluster name
      }

      this.glob.getContextNode().setInstanceName(newServerNodeInstanceName);
      if (clusterContext == null) {
         clusterContext = this.glob.getContextNode();
         String ln = getLoginName();
         if (ln != null && ln.length() > 0) {
            String instanceName = this.glob.validateJmxValue(ln);
            ContextNode contextNodeSubject = new ContextNode(ContextNode.CONNECTION_MARKER_TAG, instanceName, clusterContext);
            this.contextNode = new ContextNode(ContextNode.SESSION_MARKER_TAG, ""+getPublicSessionId(), contextNodeSubject);
         }
      }
      else {
         clusterContext.setInstanceName(newServerNodeInstanceName);
      }

      this.glob.setScopeContextNode(this.contextNode);

      try {
         // Query all "org.xmlBlaster:nodeClass=node,node=clientSUB1" + ",*" sub-nodes and replace the name by "heron"
         // For example our connectionQueue or our plugins like Pop3Driver
         if (oldClusterObjectName.length() > 0) {
            int num = this.glob.getJmxWrapper().renameMBean(oldClusterObjectName, ContextNode.CLUSTER_MARKER_TAG, this.contextNode);
            if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Renamed " + num + " jmx nodes to new '" + nodeId + "'");
         }

         if (this.mbeanHandle == null && this.contextNode != null) {   // "org.xmlBlaster:nodeClass=node,node=heron"
            this.mbeanHandle = this.glob.registerMBean(this.contextNode, this);
         }
      }
      catch (XmlBlasterException e) {
          log.warning(getLogId()+"Ignoring problem during JMX registration: " + e.toString());
      }

      setCheckpointContext(getLogId());
   }

   private void setCheckpointContext(String id) {
      if (id == null || id.length() < 1) {
         this.checkPointContext = null;
         return;
      }
      this.checkPointContext = new String[] { "sessionName", id };
   }

   /**
    * Put the given message entry into the queue
    */
   private Object queueMessage(MsgQueueEntry entry) throws XmlBlasterException {
      try {
         final I_Checkpoint cp = glob.getCheckpointPlugin();
         if (cp != null) {
            cp.passingBy(I_Checkpoint.CP_CONNECTION_PUBLISH_ENTER, entry.getMsgUnit(), null, this.checkPointContext);
         }
         this.clientQueue.put(entry, I_Queue.USE_PUT_INTERCEPTOR);
         if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Forwarded one '" + entry.getEmbeddedType() + "' message, current state is " + getState().toString());
         return entry.getReturnObj();
      }
      catch (XmlBlasterException e) {
         if (log.isLoggable(Level.FINE)) log.fine(e.getMessage());
         throw e;
      }
      catch (Throwable e) {
         if (log.isLoggable(Level.FINE)) log.fine(e.toString());
         XmlBlasterException xmlBlasterException = XmlBlasterException.convert(glob,null,null,e);
         //msgErrorHandler.handleError(new MsgErrorInfo(glob, entry, null, xmlBlasterException));
         throw xmlBlasterException; // internal errors or not in failsafe mode: throw back to client
      }
   }

   /**
    * @see I_XmlBlasterAccess#subscribe(SubscribeKey, SubscribeQos)
    */
   public SubscribeReturnQos subscribe(java.lang.String xmlKey, java.lang.String qos) throws XmlBlasterException {
      return subscribe(new SubscribeKey(glob, glob.getQueryKeyFactory().readObject(xmlKey)),
                       new SubscribeQos(glob, glob.getQueryQosFactory().readObject(qos)) );
   }

   /**
    * @see I_XmlBlasterAccess#subscribe(SubscribeKey, SubscribeQos)
    */
   public SubscribeReturnQos subscribe(SubscribeKey subscribeKey, SubscribeQos subscribeQos) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "subscribe");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      if (getSessionName().isPubSessionIdUser() &&
          subscribeQos.getData().getMultiSubscribe()==false &&
          !subscribeQos.getData().hasSubscriptionId()) {
          // For failsave clients we generate on client side the subscriptionId
          // In case of offline/clientSideQueued operation we guarantee like this a not changing
          // subscriptionId and the client code can reliably use the subscriptionId for further dispatching
          // of update() messages.
          subscribeQos.getData().generateSubscriptionId(getSessionName(), subscribeKey.getData());
      }
      MsgQueueSubscribeEntry entry  = new MsgQueueSubscribeEntry(glob,
                                      this.clientQueue.getStorageId(), subscribeKey.getData(), subscribeQos.getData());
      return (SubscribeReturnQos)queueMessage(entry);
   }

   /**
    * @see I_XmlBlasterAccess#subscribe(SubscribeKey, SubscribeQos, I_Callback)
    */
   public SubscribeReturnQos subscribe(java.lang.String xmlKey, java.lang.String qos, I_Callback cb) throws XmlBlasterException {
      return subscribe(new SubscribeKey(glob, glob.getQueryKeyFactory().readObject(xmlKey)),
                       new SubscribeQos(glob, glob.getQueryQosFactory().readObject(qos)),
                       cb );
   }

   /**
    * @see I_XmlBlasterAccess#subscribe(SubscribeKey, SubscribeQos, I_Callback)
    */
   public SubscribeReturnQos subscribe(SubscribeKey subscribeKey, SubscribeQos subscribeQos, I_Callback cb) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "subscribe");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      if (this.updateListener == null) {
         String text = "No callback listener is registered. " +
                       " Please use XmlBlasterAccess.connect() with default I_Callback given.";
         throw new XmlBlasterException(glob, ErrorCode.INTERNAL_ILLEGALARGUMENT, ME, text);
      }

      // sync subscribe & put against update()'s check for entry
      // otherwise if the update was faster then the subscribe to return we miss the entry
      synchronized (this.updateDispatcher) {
         SubscribeReturnQos subscribeReturnQos = subscribe(subscribeKey, subscribeQos);
         this.updateDispatcher.addCallback(subscribeReturnQos.getSubscriptionId(), cb, subscribeQos.getPersistent());
         if (!subscribeReturnQos.isFakedReturn()) {
            this.updateDispatcher.ackSubscription(subscribeReturnQos.getSubscriptionId());
         }
         return subscribeReturnQos;
      }
   }

   /**
    * @see I_XmlBlasterAccess#get(GetKey, GetQos)
    */
   public MsgUnit[] get(java.lang.String xmlKey, java.lang.String qos) throws XmlBlasterException {
      return get(new GetKey(glob, glob.getQueryKeyFactory().readObject(xmlKey)),
                 new GetQos(glob, glob.getQueryQosFactory().readObject(qos)) );
   }

   /**
    * @see I_XmlBlasterAccess#getCached(GetKey, GetQos)
    */
   public MsgUnit[] getCached(GetKey getKey, GetQos getQos) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "getCached");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      if (this.synchronousCache == null) {  //Is synchronousCache installed?
         throw new XmlBlasterException(glob, ErrorCode.USER_CONFIGURATION, ME,
              "Can't handle getCached(), please install a cache with createSynchronousCache() first");
      }

      MsgUnit[] msgUnitArr = null;
      msgUnitArr = this.synchronousCache.get(getKey, getQos);
      if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"CacheDump: msgUnitArr=" + msgUnitArr + ": '" + getKey.toXml().trim() + "' \n" + getQos.toXml() + this.synchronousCache.toXml(""));
      //not found in this.synchronousCache
      if(msgUnitArr == null) {
         msgUnitArr = get(getKey, getQos)//get messages from xmlBlaster (synchronous)
         SubscribeKey subscribeKey = new SubscribeKey(glob, getKey.getData());
         SubscribeQos subscribeQos = new SubscribeQos(glob, getQos.getData());
         SubscribeReturnQos subscribeReturnQos = null;
         synchronized (this.synchronousCache) {
            subscribeReturnQos = subscribe(subscribeKey, subscribeQos); //subscribe to this messages (asynchronous)
            this.synchronousCache.newEntry(subscribeReturnQos.getSubscriptionId(), getKey, msgUnitArr);     //fill messages to this.synchronousCache
         }
         log.info(getLogId()+"New entry in this.synchronousCache created (subscriptionId="+subscribeReturnQos.getSubscriptionId()+")");
      }
      return msgUnitArr;
   }

   /**
    * @see I_XmlBlasterAccess#get(GetKey, GetQos)
    */
   public MsgUnit[] get(GetKey getKey, GetQos getQos) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "get");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      MsgQueueGetEntry entry  = new MsgQueueGetEntry(glob,
                                      this.clientQueue.getStorageId(), getKey, getQos);
      MsgUnit[] arr = (MsgUnit[])queueMessage(entry);
      return (arr == null) ? new MsgUnit[0] : arr;
   }

   /**
    * @see I_XmlBlasterAccess#unSubscribe(UnSubscribeKey, UnSubscribeQos)
    */
   public UnSubscribeReturnQos[] unSubscribe(UnSubscribeKey unSubscribeKey, UnSubscribeQos unSubscribeQos) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "unSubscribe");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      MsgQueueUnSubscribeEntry entry  = new MsgQueueUnSubscribeEntry(glob,
                                      this.clientQueue.getStorageId(), unSubscribeKey, unSubscribeQos);
      UnSubscribeReturnQos[] arr = (UnSubscribeReturnQos[])queueMessage(entry);
      this.updateDispatcher.removeCallback(unSubscribeKey.getOid());
      return (arr == null) ? new UnSubscribeReturnQos[0] : arr;
   }

   /**
    * @see I_XmlBlasterAccess#unSubscribe(UnSubscribeKey, UnSubscribeQos)
    */
   public UnSubscribeReturnQos[] unSubscribe(java.lang.String xmlKey, java.lang.String qos) throws XmlBlasterException {
      return unSubscribe(new UnSubscribeKey(glob, glob.getQueryKeyFactory().readObject(xmlKey)),
                       new UnSubscribeQos(glob, glob.getQueryQosFactory().readObject(qos)) );
   }

   /**
    * @see I_XmlBlasterAccess#publish(MsgUnit)
    */
   public PublishReturnQos publish(MsgUnit msgUnit) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "publish");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      MsgQueuePublishEntry entry  = new MsgQueuePublishEntry(glob, msgUnit, this.clientQueue.getStorageId());
      return (PublishReturnQos)queueMessage(entry);
   }

   /**
    * @see I_XmlBlasterAccess#publishOneway(MsgUnit[])
    */
   public void publishOneway(org.xmlBlaster.util.MsgUnit [] msgUnitArr) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "publishOneway");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      final boolean ONEWAY = true;
      for (int ii=0; ii<msgUnitArr.length; ii++) {
         MsgQueuePublishEntry entry  = new MsgQueuePublishEntry(glob, msgUnitArr[ii],
                                          this.clientQueue.getStorageId(), ONEWAY);
         queueMessage(entry);
      }
   }

   // rename to publish()
   public PublishReturnQos[] publishArr(org.xmlBlaster.util.MsgUnit[] msgUnitArr) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "publishArr");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      if (this.firstWarn) {
         log.warning(getLogId()+"Publishing arrays is not atomic implemented - TODO");
         this.firstWarn = false;
      }
      PublishReturnQos[] retQos = new PublishReturnQos[msgUnitArr.length];
      for (int ii=0; ii<msgUnitArr.length; ii++) {
         MsgQueuePublishEntry entry  = new MsgQueuePublishEntry(glob, msgUnitArr[ii],
                                          this.clientQueue.getStorageId());
         retQos[ii] = (PublishReturnQos)queueMessage(entry);
      }
      return retQos;
   }

   /**
    * @see I_XmlBlasterAccess#erase(EraseKey, EraseQos)
    */
   public EraseReturnQos[] erase(EraseKey eraseKey, EraseQos eraseQos) throws XmlBlasterException {
      if (!this.isValid)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "erase");
      if (!isConnected()) throw new XmlBlasterException(glob, ErrorCode.USER_NOT_CONNECTED, ME);
      MsgQueueEraseEntry entry  = new MsgQueueEraseEntry(glob,
                                      this.clientQueue.getStorageId(), eraseKey, eraseQos);
      EraseReturnQos[] arr = (EraseReturnQos[])queueMessage(entry);
      return (arr == null) ? new EraseReturnQos[0] : arr;
   }

   /**
    * @see I_XmlBlasterAccess#erase(EraseKey, EraseQos)
    */
   public EraseReturnQos[] erase(java.lang.String xmlKey, java.lang.String qos) throws XmlBlasterException {
      return erase(new EraseKey(glob, glob.getQueryKeyFactory().readObject(xmlKey)),
                       new EraseQos(glob, glob.getQueryQosFactory().readObject(qos)) );
   }
  
   /**
    * For example called by SOCKET layer (SocketCallbackImpl.java) on EOF.
    * Does immediate ping to go to polling mode
    * @param xmlBlasterException
    * @see org.xmlBlaster.client.I_CallbackExtended#lostConnection(XmlBlasterException)
    */
   public void lostConnection(XmlBlasterException xmlBlasterException) {
      if (log.isLoggable(Level.FINE)) log.fine("Communication layer lost connection: " + ((xmlBlasterException==null)?"":xmlBlasterException.toString()));
      this.dispatchManager.pingCallbackServer(false);
   }

   /**
    * Force a async ping to re-check connection to server. Status change can be
    * got asynchronously via registerConnectionListener()
    */
   public void ping() {
      this.dispatchManager.pingCallbackServer(false);
   }

   /**
    * This is the callback method invoked from xmlBlaster
    * delivering us a new asynchronous message.
    * @see org.xmlBlaster.client.I_Callback#update(String, UpdateKey, byte[], UpdateQos)
    */
   public String update(String cbSessionId, UpdateKey updateKey, byte[] content, UpdateQos updateQos) throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Entering update(updateKey=" + updateKey.getOid() +
                    ", subscriptionId=" + updateQos.getSubscriptionId() + ", " + ((this.synchronousCache != null) ? "using synchronousCache" : "no synchronousCache") + ") ...");

      if (this.synchronousCache != null) {
         boolean retVal;
         synchronized (this.synchronousCache) {
            retVal = this.synchronousCache.update(updateQos.getSubscriptionId(), updateKey, content, updateQos);
         }
         if (retVal) {
            if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Putting update message " + updateQos.getSubscriptionId() + " into cache");
            return Constants.RET_OK; // "<qos><state id='OK'/></qos>";
         }
         if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Update message " + updateQos.getSubscriptionId() + " is not for cache");
      }

      Object obj = null;
      // sync against subscribe & put
      // otherwise if the update was faster then the subscribe to return we miss the entry
      synchronized (this.updateDispatcher) {
         obj = this.updateDispatcher.getCallback(updateQos.getSubscriptionId());
      }

      if (obj != null) {  // If a special callback was specified for this subscription:
         I_Callback cb = (I_Callback)obj;
         return cb.update(cbSessionId, updateKey, content, updateQos); // deliver the update to our client
      }
      else if (this.updateListener != null) {
         // If a general callback was specified on login:
         return this.updateListener.update(cbSessionId, updateKey, content, updateQos); // deliver the update to our client
      }
      else {
         log.severe(getLogId()+"Ignoring unexpected update message as client has not registered a callback: " + updateKey.toXml() + "" + updateQos.toXml());
      }

      return Constants.RET_OK; // "<qos><state id='OK'/></qos>";
   }

   /**
    * Call by DispatchManager on connection state transition.
    * <p />
    * Enforced by interface I_ConnectionStatusListener
    */
   public void toAlive(DispatchManager dispatchManager, ConnectionStateEnum oldState) {
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Changed from connection state " + oldState + " to " + ConnectionStateEnum.ALIVE + " connectInProgress=" + this.connectInProgress);
      if (this.clientQueue != null && this.clientQueue.getNumOfEntries() > 0) {
         log.info(getLogId()+"Changed from connection state " + oldState + " to " + ConnectionStateEnum.ALIVE +
                      " connectInProgress=" + this.connectInProgress +
                      " with " + this.clientQueue.getNumOfEntries() + " client side queued messages");
      }
      if (this.connectInProgress) {
         dispatchManager.trySyncMode(true);
         if (this.clientQueue != null && this.clientQueue.getNumOfEntries() > 0) {
            try {
               MsgQueueEntry entry = (MsgQueueEntry)this.clientQueue.peek();
               if (entry.getMethodName() == MethodName.CONNECT) {
                  this.clientQueue.remove();
                  log.info(getLogId()+"Removed queued connect message, our new connect has precedence");
               }
            }
            catch (XmlBlasterException e) {
               log.severe(getLogId()+"Removing connect entry in client tail back queue failed: " + e.getMessage() + "\n" + toXml());
            }
         }
         return;
      }

      if (this.clientQueue == null || this.clientQueue.getNumOfEntries() == 0) {
         dispatchManager.trySyncMode(true);
      }

      if (this.connectReturnQos == null || !this.connectReturnQos.isReconnected()) {
         cleanupForNewServer();
      }

      if (this.connectionListener != null) {
         this.connectionListener.reachedAlive(oldState, this);
      }
   }

   public void toAliveSync(DispatchManager dispatchManager, ConnectionStateEnum oldState) {
      if (this.connectionListener != null) {
         this.connectionListener.reachedAliveSync(oldState, this);
      }
   }
  
   /**
    * If we have reconnected to xmlBlaster and the xmlBlaster server instance
    * is another one which does not know our session state and subscribes we need to clear all
    * cached subscribes etc.
    */
   private void cleanupForNewServer() {
      if (this.updateDispatcher.size() > 0) {
         int num = this.updateDispatcher.clearAckNonPersistentSubscriptions(); // to avoid memory leaks, subscribes pending in the queue are not cleared
         if (num > 0) {
            log.info(getLogId()+"Removed " + num + " subscribe specific callback registrations");
         }
         // TODO: On switch to sync delivery and the client has
         // cleared subscribes from the queue manually we have still a memory leak here:
         // We would need to call clearNAKSubscriptions()
      }
      if (this.synchronousCache != null) {
         this.synchronousCache.clear(); // we need to re-subscribe
      }
   }

   /**
    * Call by DispatchManager on connection state transition.
    * <p />
    * Enforced by interface I_ConnectionStatusListener
    */
   public void toPolling(DispatchManager dispatchManager, ConnectionStateEnum oldState) {
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Changed from connection state " + oldState + " to " + ConnectionStateEnum.POLLING + " connectInProgress=" + this.connectInProgress);
      if (this.connectInProgress) return;
      if (this.connectionListener != null) {
         this.connectionListener.reachedPolling(oldState, this);
      }
   }

   /**
    * Workaround to transport the reason for the toDead() transition as
    * the interface {@link I_ConnectionStateListener#reachedDead(ConnectionStateEnum, I_XmlBlasterAccess) is missing
    * to pass the exception to the client.
    * <p>
    * Currently the client needs a downcast to XmlBlasterAccess (not in I_XmlBlasterAccess)
    * @return Can be null
    */
   public XmlBlasterException getToDeadXmlBlasterException() {
    return toDeadXmlBlasterException;
  }

   /**
    * Call by DispatchManager on connection state transition.
    * <p>Enforced by interface I_ConnectionStatusListener</p>
    */
   public void toDead(DispatchManager dispatchManager, ConnectionStateEnum oldState, XmlBlasterException xmlBlasterException) {
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Changed from connection state " + oldState + " to " + ConnectionStateEnum.DEAD + " connectInProgress=" + this.connectInProgress);
      if (this.connectionListener != null) {
       this.toDeadXmlBlasterException = xmlBlasterException; // hack, description see #getToDeadXmlBlasterException
         this.connectionListener.reachedDead(oldState, this);
      }
   }

   /**
    * Access the environment settings of this connection.
    * <p>Enforced by interface I_XmlBlasterAccess</p>
    * @return The global handle (like a stack with local variables for this connection)
    */
   public Global getGlobal() {
      return this.glob;
   }

   /**
    * <p>Enforced by interface I_ConnectionHandler</p>
    * @return The queue used to store tailback messages.
    */
   public I_Queue getQueue() {
      return this.clientQueue;
   }

   /**
    * <p>Enforced by interface I_ConnectionHandler</p>
    * @return The current state of the connection
    */
   public ConnectionStateEnum getState() {
      if (!isConnected()) return ConnectionStateEnum.UNDEF;
      return this.dispatchManager.getDispatchConnectionsHandler().getState();
   }

   /**
    * Get the connection state.
    * String version for JMX access.
    * @return "UNDEF", "ALIVE", "POLLING", "DEAD"
    */
   public String getConnectionState() {
      return getState().toString();
   }

   /**
    * <p>Enforced by interface I_ConnectionHandler</p>
    * @return true if the connection to xmlBlaster is operational
    */
   public boolean isAlive() {
      if (!isConnected()) return false;
      return this.dispatchManager.getDispatchConnectionsHandler().isAlive();
   }

   /**
    * <p>Enforced by interface I_ConnectionHandler</p>
    * @return true if we are polling for the server
    */
   public boolean isPolling() {
      if (!isConnected()) return false;
      return this.dispatchManager.getDispatchConnectionsHandler().isPolling();
   }

   /**
    * <p>Enforced by interface I_ConnectionHandler</p>
    * @return true if we have definitely lost the connection to xmlBlaster and gave up
    */
   public boolean isDead() {
      if (!isConnected()) return false;
      return this.dispatchManager.getDispatchConnectionsHandler().isDead();
   }

   /**
    * Access the returned QoS of a connect() call.
    * <p>Enforced by interface I_XmlBlasterAccess</p>
    * @return Can be null if not connected
    */
   public ConnectReturnQos getConnectReturnQos() {
      return this.connectReturnQos;
   }

   /**
    * Access the current ConnectQos
    * <p>Enforced by interface I_XmlBlasterAccess</p>
    * @return Can be null if not connected
    */
   public ConnectQos getConnectQos() {
      return this.connectQos;
   }

   /**
    * @return null if no callback is configured
    */
   public final DispatchStatistic getDispatchStatistic() {
      if (this.statistic == null) {
         synchronized (this) {
            if (this.statistic == null) {
               if (this.dispatchManager != null)
                  this.statistic = this.dispatchManager.getDispatchStatistic();
               else
                  this.statistic = new DispatchStatistic();
            }
         }
      }
      return this.statistic;
   }

   /**
    * Access the login name.
    * @return your login name or null if you are not logged in
    */
   public synchronized final String getLoginName() {
      SessionName sn = getSessionName();
      if (sn == null) return "xmlBlasterClient";
      return sn.getLoginName();
      /*
      //if (this.connectReturnQos != null)
      //   return this.connectReturnQos.getLoginName();
      //try {
         if (connectQos != null && connectQos.getSecurityQos() != null) {
            String nm = connectQos.getSecurityQos().getUserId();
            if (nm != null && nm.length() > 0)
               return nm;
         }
      //}
      //catch (XmlBlasterException e) {}
      return glob.getId(); // "client?";
      */
   }

   public final boolean isCallbackConfigured() {
      return (this.cbServer != null);
   }

   public final long getUptime() {
      return (System.currentTimeMillis() - this.startupTime)/1000L;
   }

   public final String getLoginDate() {
      long ll = this.startupTime;
      java.sql.Timestamp tt = new java.sql.Timestamp(ll);
      return tt.toString();
   }

   public synchronized final long getPublicSessionId() {
      SessionName sn = getSessionName();
      if (sn == null) return 0;
      return sn.getPublicSessionId();
   }

   public final long getNumPublish() {
      return getDispatchStatistic().getNumPublish();
   }

   public final long getNumSubscribe() {
      return getDispatchStatistic().getNumSubscribe();
   }

   public final long getNumUnSubscribe() {
      return getDispatchStatistic().getNumUnSubscribe();
   }

   public final long getNumGet() {
      return getDispatchStatistic().getNumGet();
   }

   public final long getNumErase() {
      return getDispatchStatistic().getNumErase();
   }

   public final long getNumUpdateOneway() {
      return getDispatchStatistic().getNumUpdateOneway();
   }

   public final long getNumUpdate() {
      return getDispatchStatistic().getNumUpdate();
   }

   public synchronized final long getConnectionQueueNumMsgs() {
      if (this.clientQueue == null) return 0L;
      return this.clientQueue.getNumOfEntries();
   }

   public synchronized final long getConnectionQueueMaxMsgs() {
      if (this.clientQueue == null) return 0L;
      return this.clientQueue.getMaxNumOfEntries();
   }

   public final long getPingRoundTripDelay() {
      return getDispatchStatistic().getPingRoundTripDelay();
   }

   public final long getRoundTripDelay() {
      return getDispatchStatistic().getRoundTripDelay();
   }

   /** JMX **/
   public String invokePublish(String key, String content, String qos) throws Exception {
      if (key == null || key.length()==0 || key.equalsIgnoreCase("String"))
         throw new IllegalArgumentException("Please pass a valid XML key like '<key oid='Hello'/> or the simple oid like 'Hello'");
      if (key.indexOf("<") == -1) {
         key = "<key oid='" + key + "'/>";
      }
      qos = checkQueryKeyQos(key, qos);
      if (content == null) content = "";
      try {
         MsgUnit msgUnit = new MsgUnit(key, content, qos);
         PublishReturnQos prq = publish(msgUnit);
         return prq.toString();
      }
      catch (XmlBlasterException e) {
         throw new Exception(e.toString());
      }
   }

   private String checkQueryKeyQos(String url, String qos) {
      if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"url=" + url + " qos=" + qos);
      if (url == null || url.length()==0 || url.equalsIgnoreCase("String"))
         throw new IllegalArgumentException("Please pass a valid URL like 'xpath://key' or a simple oid like 'Hello'");
      if (qos == null || qos.length()==0 || qos.equalsIgnoreCase("String")) qos = "<qos/>";
      return qos;
   }

   /** JMX **/
   public String[] invokeUnSubscribe(String url, String qos) throws Exception {
      qos = checkQueryKeyQos(url, qos);
      try {
         UnSubscribeKey usk = new UnSubscribeKey(glob, url);
         UnSubscribeReturnQos[] usrq = unSubscribe(usk, new UnSubscribeQos(glob, glob.getQueryQosFactory().readObject(qos)));
         if (usrq == null) return new String[0];
         String[] ret = new String[usrq.length];
         if (ret.length < 1) {
            return new String[] { "unSubscribe '"+url+"' did not match any subscription" };
         }
         for (int i=0; i<usrq.length; i++) {
            ret[i] = usrq[i].toXml();
         }
         return ret;
      }
      catch (XmlBlasterException e) {
         throw new Exception(e.toString());
      }
   }

   /** JMX **/
   public String invokeSubscribe(String url, String qos) throws Exception {
      qos = checkQueryKeyQos(url, qos);
      try {
         SubscribeKey usk = new SubscribeKey(glob, url);
         SubscribeReturnQos srq = subscribe(usk, new SubscribeQos(glob, glob.getQueryQosFactory().readObject(qos)));
         if (srq == null) return "";
         return srq.toXml();
      }
      catch (XmlBlasterException e) {
         throw new Exception(e.toString());
      }
   }

   /** JMX **/
   public String[] invokeGet(String url, String qos) throws Exception {
      qos = checkQueryKeyQos(url, qos);
      try {
         GetKey gk = new GetKey(glob, url);
         MsgUnit[] msgs = get(gk, new GetQos(glob, glob.getQueryQosFactory().readObject(qos)));
         if (msgs == null) return new String[0];
         if (msgs == null || msgs.length < 1) {
            return new String[] { "get('"+url+"') did not match any topic" };
         }
         ArrayList tmpList = new ArrayList();
         for (int i=0; i<msgs.length; i++) {
            tmpList.add("  "+msgs[i].getKeyData().toXml());
            tmpList.add("  "+msgs[i].getContentStr());
            tmpList.add("  "+msgs[i].getQosData().toXml());
         }
         return (String[])tmpList.toArray(new String[tmpList.size()]);
      }
      catch (XmlBlasterException e) {
         throw new Exception(e.toString());
      }
   }

   /** JMX **/
   public String[] invokeErase(String url, String qos) throws Exception {
      qos = checkQueryKeyQos(url, qos);
      try {
         EraseKey ek = new EraseKey(glob, url);
         EraseReturnQos[] erq = erase(ek, new EraseQos(glob, glob.getQueryQosFactory().readObject(qos)));
         if (erq == null) return new String[0];
         String[] ret = new String[erq.length];
         if (ret.length < 1) {
            return new String[] { "erase('"+url+"') did not match any topic, nothing is erased." };
         }
         for (int i=0; i<erq.length; i++) {
            ret[i] = erq[i].toXml();
         }
         return ret;
      }
      catch (XmlBlasterException e) {
         throw new Exception(e.toString());
      }
   }

   /**
    * Sets the DispachManager belonging to this session to active or inactive.
    * It is initially active. Setting it to false temporarly inhibits dispatch of
    * messages which are in the callback queue. Setting it to true starts the
    * dispatch again.
    * @param dispatchActive
    */
   public synchronized void setDispatcherActive(boolean dispatcherActive) {
      if (this.dispatchManager != null) {
         this.dispatchManager.setDispatcherActive(dispatcherActive);
      }
   }

   public synchronized  boolean getDispatcherActive() {
      if (this.dispatchManager != null) {
         return this.dispatchManager.isDispatcherActive();
      }
      return false;
   }

   public void setCallbackDispatcherActive(boolean activate) throws XmlBlasterException {
      if (this.streamingCb != null && !isCallbackDispatcherActive() && activate) {
         int ret = this.streamingCb.sendInitialQueueEntries();
         log.info("locally retrieved '" + ret + "' chunks");
      }

      String command = getSessionName() + "/?dispatcherActive=" + activate;
      sendAdministrativeCommand(command);
      this.connectQos.getSessionCbQueueProperty().getCurrentCallbackAddress().setDispatcherActive(activate);
   }

   public boolean isCallbackDispatcherActive() throws XmlBlasterException {
      String command = getSessionName() + "/?dispatcherActive";
      boolean ret = "true".equalsIgnoreCase(sendAdministrativeCommand(command));
      return ret;
   }

   public String sendAdministrativeCommand(String command) throws XmlBlasterException {
      if (command == null)
         throw new IllegalArgumentException("sendAdministrativeCommand() called with null argument");
      command = command.trim();
      boolean isGet = command.indexOf("get ") == 0 || command.indexOf("GET ") == 0;
      boolean isSet = command.indexOf("set ") == 0 || command.indexOf("SET ") == 0;
      String cmd = ((isGet || isSet)) ? command.substring(4) : command;

      if (isSet || (!isGet && cmd.indexOf("=") != -1)) {
         String oid = "__cmd:" + cmd;
         PublishKey key = new PublishKey(glob, oid); // oid="__cmd:/client/joe/1/?dispatcherActive=false"
         PublishQos qos = new PublishQos(glob);
         MsgUnit msgUnit = new MsgUnit(key, "", qos);
         try {
            PublishReturnQos ret = publish(msgUnit);
            if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Send '" + cmd + " '");
            return ret.getState();
         }
         catch (XmlBlasterException e) {
            if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Sending of '" + cmd + " ' failed: " + e.getMessage());
            throw e;
         }
      }
      else {
         String oid = "__cmd:" + cmd;
         GetKey getKey = new GetKey(glob, oid);
         GetQos getQos = new GetQos(glob);
         try {
            MsgUnit[] msgs = get(getKey, getQos);
            if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Send '" + cmd + " ', got array of size " + msgs.length);
            if (msgs.length == 0)
               return "";
            return msgs[0].getContentStr();
         }
         catch (XmlBlasterException e) {
            if (log.isLoggable(Level.FINE)) log.fine(getLogId()+"Sending of '" + cmd + " ' failed: " + e.getMessage());
            throw e;
         }
      }
   }

   public synchronized String[] peekClientMessages(int numOfEntries) throws Exception {
      try {
         if (numOfEntries == 0)
            return new String[] { "Please pass number of messages to peak" };
         if (this.clientQueue == null)
            return new String[] { "There is no client queue available" };
         if (this.clientQueue.getNumOfEntries() < 1)
            return new String[] { "The client queue is empty" };

         List<I_Entry> list = this.clientQueue.peek(numOfEntries, -1);

         if (list.size() == 0)
            return new String[] { "Peeking messages from client queue failed, the reason is not known" };

         ArrayList tmpList = new ArrayList();
         for (int i=0; i<list.size(); i++) {
            MsgQueueEntry entry = (MsgQueueEntry)list.get(i);
            if (entry instanceof MsgQueuePublishEntry) {
               MsgQueuePublishEntry pe = (MsgQueuePublishEntry)entry;
               tmpList.add("  "+pe.getMsgUnit().getKeyData().toXml());
               tmpList.add("  "+pe.getMsgUnit().getContentStr());
               tmpList.add("  "+pe.getMsgUnit().getQosData().toXml());
            }
            else if (entry instanceof MsgQueueConnectEntry) {
              MsgQueueConnectEntry pe = (MsgQueueConnectEntry)entry;
                tmpList.add("  "+pe.getConnectQosData().toXml());
            }
            else if (entry instanceof MsgQueueDisconnectEntry) {
              MsgQueueDisconnectEntry pe = (MsgQueueDisconnectEntry)entry;
                tmpList.add("  "+pe.getDisconnectQos().toXml());
            }
            else if (entry instanceof MsgQueueEraseEntry) {
              MsgQueueEraseEntry pe = (MsgQueueEraseEntry)entry;
                tmpList.add("  "+pe.getEraseKey().toXml());
                tmpList.add("  "+pe.getEraseQos().toXml());
            }
            else if (entry instanceof MsgQueueGetEntry) {
              MsgQueueGetEntry pe = (MsgQueueGetEntry)entry;
                tmpList.add("  "+pe.getGetKey().toXml());
                tmpList.add("  "+pe.getGetQos().toXml());
            }
            else if (entry instanceof MsgQueueSubscribeEntry) {
              MsgQueueSubscribeEntry pe = (MsgQueueSubscribeEntry)entry;
                tmpList.add("  "+pe.getSubscribeKeyData().toXml());
                tmpList.add("  "+pe.getSubscribeQosData().toXml());
            }
            else if (entry instanceof MsgQueueUnSubscribeEntry) {
              MsgQueueUnSubscribeEntry pe = (MsgQueueUnSubscribeEntry)entry;
                tmpList.add("  "+pe.getUnSubscribeKey().toXml());
                tmpList.add("  "+pe.getUnSubscribeQos().toXml());
            }
            else {
               tmpList.add("Unsupported message queue entry '" + entry.getClass().getName() + "'");
            }
         }

         return (String[])tmpList.toArray(new String[tmpList.size()]);
      }
      catch (XmlBlasterException e) {
         throw new Exception(e.toString());
      }
   }

   /**
    * Peek messages from client queue and dump them to a file, they are not removed.
    * @param numOfEntries The number of messages to peek, taken from the front
    * @param path The path to dump the messages to, it is automatically created if missing.
    * @return The file names of the dumped messages
    */
   public synchronized String[] peekClientMessagesToFile(int numOfEntries, String path) throws Exception {
      try {
         return this.glob.peekQueueMessagesToFile(this.clientQueue, numOfEntries, path, "client");
      }
      catch (XmlBlasterException e) {
         throw new Exception(e.toString());
      }
   }

   /**
    * Command line usage.
    */
   public static String usage(Global glob) {
      glob = (glob == null) ? Global.instance() : glob;
      StringBuffer sb = new StringBuffer(4096);
      sb.append("\n");
      sb.append("Choose a connection protocol:\n");
      sb.append("   -protocol           Specify a protocol to talk with xmlBlaster, 'SOCKET' or 'IOR' or 'RMI' or 'SOAP' or 'XMLRPC'.\n");
      sb.append("                       This is used for connection to xmlBlaster and for the callback connection.\n");
      sb.append("                       Current setting is '" + glob.getProperty().get("client.protocol", "IOR") + "'. See below for protocol settings.\n");
      sb.append("   -dispatch/connection/protocol <protocol>\n");
      sb.append("                       Specify the protocol to connect to xmlBlaster only (not for the callback).\n");
      sb.append("   -dispatch/callback/protocol <protocol>\n");
      sb.append("                       Specify the protocol for the callback connection only.\n");
      sb.append("              Example: java MyApp -protocol SOCKET\n");
      sb.append("                       java MyApp -dispatch/connection/protocol RMI -dispatch/connection/plugin/rmi/hostname 192.168.10.34\n");
      sb.append("                       java MyApp -dispatch/connection/protocol RMI -dispatch/callback/protocol XMLRPC\n");
      sb.append("\n");
      sb.append("Security features:\n");
      sb.append("   -Security.Client.DefaultPlugin \"htpasswd,1.0\"\n");
      sb.append("                       Force the given authentication schema, here the 'htpasswd' is enforced\n");
      sb.append("                       Clients can overwrite this with ConnectQos.java\n");
      try {
      sb.append(new org.xmlBlaster.client.qos.ConnectQos(glob).usage());
      } catch (XmlBlasterException e) {}
      sb.append(new org.xmlBlaster.util.qos.address.Address(glob).usage());
      sb.append(new org.xmlBlaster.util.qos.storage.ClientQueueProperty(glob,null).usage());
      sb.append(new org.xmlBlaster.util.qos.address.CallbackAddress(glob).usage());
      sb.append(new org.xmlBlaster.util.qos.storage.CbQueueProperty(glob,null,null).usage());
      sb.append(new org.xmlBlaster.util.qos.storage.HistoryQueueProperty(glob,null).usage("Control the default size of the history queue for each topic (send with publish calls)"));
      sb.append(getPluginUsage("org.xmlBlaster.client.protocol.socket.SocketConnection"));
      sb.append(getPluginUsage("org.xmlBlaster.client.protocol.corba.CorbaConnection"));
      sb.append(getPluginUsage("org.xmlBlaster.client.protocol.rmi.RmiConnection"));
      sb.append(getPluginUsage("org.xmlBlaster.client.protocol.xmlrpc.XmlRpcConnection"));
      //sb.append(org.xmlBlaster.util.Global.instance().usage()); // for Logger help
      return sb.toString();
   }

   /**
    * Access plugin specific usage()
    * @return if plugin is not in CLASSPATH return empty string
    */
   public static String getPluginUsage(String clazzName) {
      try {
         Class clazz = java.lang.Class.forName(clazzName);
         if (clazz != null) {
            Class[] paramCls = new Class[0];
            Object[] params = new Object[0];
            java.lang.reflect.Method method = clazz.getMethod("usage", paramCls);
            String tmp = (String)method.invoke(clazz, params);
            return tmp;
         }
      }
      catch (Exception ex) { // java.lang.ClassNotFoundException:
      }
      return "";
   }

   /**
    * Dump state of this object into a XML ASCII string.
    * <br>
    * @return internal state of SubjectInfo as a XML ASCII string
    */
   public final String toXml() {
      return toXml((String)null);
   }

   /**
    * Dump state of this object into a XML ASCII string.
    * <br>
    * @param extraOffset indenting of tags for nice output
    * @return internal state of SubjectInfo as a XML ASCII string
    */
   public final String toXml(String extraOffset) {
      StringBuffer sb = new StringBuffer(1024);
      if (extraOffset == null) extraOffset = "";
      String offset = Constants.OFFSET + extraOffset;

      sb.append(offset).append("<XmlBlasterAccess id='").append(this.getId());
      if (this.dispatchManager != null && this.dispatchManager.getDispatchConnectionsHandler() != null) {
         sb.append("' state='").append(this.dispatchManager.getDispatchConnectionsHandler().getState());
      }
      sb.append("'>");
      sb.append(offset).append(" <connected>").append(isConnected()).append("</connected>");
      sb.append(offset).append("</XmlBlasterAccess>");

      return sb.toString();
   }

   /**
    *  For testing invoke: java org.xmlBlaster.client.XmlBlasterAccess
    */
   public static void main( String[] args ) {
      try {
         final Global glob = new Global(args);
         final String oid = "HelloWorld";

         final I_XmlBlasterAccess xmlBlasterAccess = glob.getXmlBlasterAccess();

         /*
         try {
            log.info(ME, "Hit a key to subscribe on topic " + oid);
            try { System.in.read(); } catch(java.io.IOException e) {}
            SubscribeKey sk = new SubscribeKey(glob, oid);
            SubscribeQos sq = new SubscribeQos(glob);
            SubscribeReturnQos subRet = xmlBlasterAccess.subscribe(sk, sq);
            log.info(ME, "Subscribed for " + sk.toXml() + "\n" + sq.toXml() + " return:\n" + subRet.toXml());
         }
         catch(XmlBlasterException e) {
            log.error(ME, e.getMessage());
         }
         */

         xmlBlasterAccess.registerConnectionListener(new I_ConnectionStateListener() {
            public void reachedAlive(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
               log.severe("DEBUG ONLY: Changed from connection state " + oldState + " to " + ConnectionStateEnum.ALIVE + " with " + connection.getQueue().getNumOfEntries() + " queue entries pending");
            }
            public void reachedPolling(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
               log.severe("DEBUG ONLY: Changed from connection state " + oldState + " to " + ConnectionStateEnum.POLLING);
            }
            public void reachedDead(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
               log.severe("DEBUG ONLY: Changed from connection state " + oldState + " to " + ConnectionStateEnum.DEAD);
            }
            public void reachedAliveSync(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
               log.severe("DEBUG ONLY: Changed from connection state " + oldState + " to " + ConnectionStateEnum.ALIVE + " in sync");
            }
         });

         ConnectReturnQos connectReturnQos = xmlBlasterAccess.connect(null, new I_Callback() {
               public String update(String cbSessionId, UpdateKey updateKey, byte[] content, UpdateQos updateQos) {
                  log.info("UPDATE: Receiving asynchronous callback message " + updateKey.toXml() + "\n" + updateQos.toXml());
                  return "";
               }
            })// Login to xmlBlaster, default handler for updates
         if (xmlBlasterAccess.isAlive()) {
            log.info("Successfully connected to xmlBlaster");
         }
         else {
            log.info("We continue in fail safe mode: " + connectReturnQos.toXml());
         }

         {
            log.info("Hit a key to subscribe on topic " + oid);
            try { System.in.read(); } catch(java.io.IOException e) {}
            SubscribeKey sk = new SubscribeKey(glob, oid);
            SubscribeQos sq = new SubscribeQos(glob);
            SubscribeReturnQos subRet = xmlBlasterAccess.subscribe(sk, sq);
            log.info("Subscribed for " + sk.toXml() + "\n" + sq.toXml() + " return:\n" + subRet.toXml());

            log.info("Hit a key to publish '" + oid + "'");
            try { System.in.read(); } catch(java.io.IOException e) {}
            MsgUnit msgUnit = new MsgUnit(glob, "<key oid='"+oid+"'/>", "Hi".getBytes(), "<qos><persistent>true</persistent></qos>");
            PublishReturnQos publishReturnQos = xmlBlasterAccess.publish(msgUnit);
            log.info("Successfully published message to xmlBlaster, msg=" + msgUnit.toXml() + "\n returned QoS=" + publishReturnQos.toXml());
            try { Thread.sleep(1000L); } catch( InterruptedException i) {} // wait for update

            {
               log.info("Hit a key to 3 times publishOneway '" + oid + "'");
               try { System.in.read(); } catch(java.io.IOException e) {}
               MsgUnit[] msgUnitArr = new MsgUnit[] {
                  new MsgUnit(glob, "<key oid='"+oid+"'/>", "Hi".getBytes(), "<qos><persistent>true</persistent></qos>"),
                  new MsgUnit(glob, "<key oid='"+oid+"'/>", "Hi".getBytes(), "<qos><persistent>true</persistent></qos>"),
                  new MsgUnit(glob, "<key oid='"+oid+"'/>", "Hi".getBytes(), "<qos><persistent>true</persistent></qos>")
               };
               xmlBlasterAccess.publishOneway(msgUnitArr);
               log.info("Successfully published " + msgUnitArr.length + " messages oneway");
               try { Thread.sleep(1000L); } catch( InterruptedException i) {} // wait for update
            }

            {
               log.info("Hit a key to 3 times publishArr '" + oid + "'");
               try { System.in.read(); } catch(java.io.IOException e) {}
               MsgUnit[] msgUnitArr = new MsgUnit[] {
                  new MsgUnit(glob, "<key oid='"+oid+"'/>", "Hi".getBytes(), "<qos><persistent>true</persistent></qos>"),
               new MsgUnit(glob, "<key oid='"+oid+"'/>", "Hi".getBytes(), "<qos><persistent>true</persistent></qos>"),
                  new MsgUnit(glob, "<key oid='"+oid+"'/>", "Hi".getBytes(), "<qos><persistent>true</persistent></qos>")
               };
               PublishReturnQos[] retArr = xmlBlasterAccess.publishArr(msgUnitArr);
               log.info("Successfully published " + retArr.length + " acknowledged messages");
               try { Thread.sleep(1000L); } catch( InterruptedException i) {} // wait for update
            }

            {
               log.info("Hit a key to get '" + oid + "'");
               try { System.in.read(); } catch(java.io.IOException e) {}
               GetKey gk = new GetKey(glob, oid);
               GetQos gq = new GetQos(glob);
               MsgUnit[] msgs = xmlBlasterAccess.get(gk, gq);
               log.info("Successfully got message from xmlBlaster, msg=" + msgs[0].toXml());
            }

            int numGetCached = 4;
            xmlBlasterAccess.createSynchronousCache(100);
            for (int i=0; i<numGetCached; i++) {
               log.info("Hit a key to getCached '" + oid + "' #"+i+"/"+numGetCached);
               try { System.in.read(); } catch(java.io.IOException e) {}
               GetKey gk = new GetKey(glob, oid);
               GetQos gq = new GetQos(glob);
               MsgUnit[] msgs = xmlBlasterAccess.getCached(gk, gq);
               log.info("Successfully got message from xmlBlaster, msg=" + msgs[0].toXml());
            }

            log.info("Hit a key to unSubscribe on topic '" + oid + "' and '" + subRet.getSubscriptionId() + "'");
            try { System.in.read(); } catch(java.io.IOException e) {}
            UnSubscribeKey uk = new UnSubscribeKey(glob, subRet.getSubscriptionId());
            UnSubscribeQos uq = new UnSubscribeQos(glob);
            UnSubscribeReturnQos[] unSubRet = xmlBlasterAccess.unSubscribe(uk, uq);
            log.info("UnSubscribed for " + uk.toXml() + "\n" + uq.toXml() + " return:\n" + unSubRet[0].toXml());

            log.info("Hit a key to erase on topic " + oid);
            try { System.in.read(); } catch(java.io.IOException e) {}
            EraseKey ek = new EraseKey(glob, oid);
            EraseQos eq = new EraseQos(glob);
            EraseReturnQos[] er = xmlBlasterAccess.erase(ek, eq);
            log.info("Erased for " + ek.toXml() + "\n" + eq.toXml() + " return:\n" + er[0].toXml());
         }

         int numPublish = 10;
         for (int ii=0; ii<numPublish; ii++) {
            log.info("Hit a key to publish #" + (ii+1) + "/" + numPublish);
            try { System.in.read(); } catch(java.io.IOException e) {}

            MsgUnit msgUnit = new MsgUnit(glob, "<key oid=''/>", ("Hi #"+(ii+1)).getBytes(), "<qos><persistent>true</persistent></qos>");
            PublishReturnQos publishReturnQos = xmlBlasterAccess.publish(msgUnit);
            log.info("Successfully published message #" + (ii+1) + " to xmlBlaster, msg=" + msgUnit.toXml() + "\n returned QoS=" + publishReturnQos.toXml());
         }

         log.info("Hit a key to disconnect ...");
         try { System.in.read(); } catch(java.io.IOException e) {}
         xmlBlasterAccess.disconnect(null);
      }
      catch (XmlBlasterException xmlBlasterException) {
         System.out.println("WARNING: Test failed: " + xmlBlasterException.getMessage());
      }
      catch (Throwable e) {
         e.printStackTrace();
         System.out.println("ERROR: Test failed: " + e.toString());
      }
      System.exit(0);
   }

   /**
    * The implementation which receives the callback messages.
    * @return Returns the updateListener or null if none was registered
    */
   public I_Callback getUpdateListener() {
      return this.updateListener;
   }

   /**
    * Register a listener to receive the callback messages.
    * <br />
    * Note: Usually you don't need to call this method directly
    * as you should pass your callback listener with connect().
    * @param updateListener The updateListener to set.
    */
   public void setUpdateListener(I_Callback updateListener) {
      this.updateListener = updateListener;
   }

   public String getVersion() {
      return glob.getVersion();
   }
   public String getRevisionNumber() {
      return glob.getRevisionNumber();
   }
   public String getBuildTimestamp() {
      return glob.getBuildTimestamp();
   }
   public String getBuildJavaVendor() {
      return glob.getBuildJavaVendor();
   }
   public String getBuildJavaVersion() {
      return glob.getBuildJavaVersion();
   }

   /**
    * Create a temporay topic.
    * You need to erase it yourself when not needed anymore
    * @param topicProperty Can be null (the default is no DOM entry)
    * @return The details about the created, temporary topic
    * @throws XmlBlasterException
    * @todo Automatically delete topic when session dies; don't allow other session to subscribe on it
    */
   public PublishReturnQos createTemporaryTopic(TopicProperty topicProperty) throws XmlBlasterException {
      if (topicProperty == null) {
         return createTemporaryTopic(-1, 10);
      }
      PublishKey pk = new PublishKey(glob, "");
      PublishQos pq = new PublishQos(glob);
      pq.setTopicProperty(topicProperty);
      MsgUnit msgUnit = new MsgUnit(pk, new byte[0], pq);
      PublishReturnQos prq = publish(msgUnit);
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Created temporary topic " + prq.getKeyOid());
      return prq;
   }

   public PublishReturnQos createTemporaryTopic(long destroyDelay, int historyMaxMsg) throws XmlBlasterException {
     return createTemporaryTopic(null, destroyDelay, historyMaxMsg);
   }

   /**
    *
    * @param uniqueTopicId Usually null, can be used to force a topicId.
    * e.g. topicIdPrefix="device.joe.request" -> the topic is something like "device.joe.request135823058558"
    * @param destroyDelay
    * @param historyMaxMsg
    * @return
    * @throws XmlBlasterException
    */
   public PublishReturnQos createTemporaryTopic(String uniqueTopicId, long destroyDelay, int historyMaxMsg) throws XmlBlasterException {
    if (uniqueTopicId == null) uniqueTopicId = "";
      PublishKey pk = new PublishKey(glob, uniqueTopicId);
      PublishQos pq = new PublishQos(glob);
      TopicProperty topicProperty = new TopicProperty(glob);
      topicProperty.setDestroyDelay(destroyDelay);
      topicProperty.setCreateDomEntry(false);
      topicProperty.setReadonly(false);
      pq.setAdministrative(true);
      if (historyMaxMsg >= 0L) {
         HistoryQueueProperty prop = new HistoryQueueProperty(this.glob, null);
         prop.setMaxEntries(historyMaxMsg);
         topicProperty.setHistoryQueueProperty(prop);
      }
      pq.setTopicProperty(topicProperty);
      MsgUnit msgUnit = new MsgUnit(pk, new byte[0], pq);
      PublishReturnQos prq = publish(msgUnit);
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Created temporary topic " + prq.getKeyOid());
      return prq;
   }

   // TODO: add other properties, add documentation requirement
   //       Add own class to support multiple request/reply over same temporary topic
   /**
    * @see org.xmlBlaster.test.client.TestRequestResponse
    */
   public MsgUnit[] request(MsgUnit msgUnit, long timeout, int maxEntries) throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) log.finer(getLogId()+"Entering request with timeout=" + timeout);
      if (msgUnit == null)
         throw new XmlBlasterException(glob, ErrorCode.INTERNAL_ILLEGALARGUMENT, ME, "Please supply a valid msgUnit to request()");

      // Create a temporary reply topic ...
      long destroyDelay = timeout+86400000; // on client crash, cleanup after one day; //long destroyDelay = -1;
      // optional, "device.joe.response" -> can be useful for performance, NOT thread safe
      boolean createResponseTopic = msgUnit.getQosData().getClientProperty("__createResponseTopic", true);
      if (createResponseTopic == false) {
          msgUnit.getQosData().getClientProperties().remove("__createResponseTopic");
      }
      String responseTopicId = msgUnit.getQosData().getClientProperty("__responseTopicId", "");
      if (responseTopicId.length() > 0) {
          msgUnit.getQosData().getClientProperties().remove("__responseTopicId");
      }
      else {
          // "device.joe.response" -> can be useful for authorization, must be distinguishable to other clients
          String responseTopicIdPrefix = msgUnit.getQosData().getClientProperty("__responseTopicIdPrefix", "");
          if (responseTopicIdPrefix.length() > 0) {
             responseTopicId = responseTopicIdPrefix + new Timestamp().getTimestamp(); // now thread safe for request()s in parallel
             msgUnit.getQosData().getClientProperties().remove("__responseTopicIdPrefix");
          }
      }
      if (createResponseTopic) {
        PublishReturnQos tempTopic = createTemporaryTopic(responseTopicId, destroyDelay, maxEntries);
          responseTopicId = tempTopic.getKeyOid();
      }
    
      try {
         // Send the request ...
         // "__jms:JMSReplyTo"
         msgUnit.getQosData().addClientProperty(Constants.addJmsPrefix(Constants.JMS_REPLY_TO, log), responseTopicId); // "__jms:JMSReplyTo"
         publish(msgUnit);
     
         // Access the reply ...
         MsgUnit[] msgs = receive("topic/"+responseTopicId, maxEntries, timeout, true);

         return msgs;
      }
      finally {
         if (createResponseTopic && responseTopicId.length() == 0) {
            // Clean up temporary topic ...
            EraseKey ek = new EraseKey(glob, responseTopicId);
            EraseQos eq = new EraseQos(glob);
            eq.setForceDestroy(true);
            erase(ek, eq);
         }
      }
   }

   public MsgUnit[] receive(String oid, int maxEntries, long timeout, boolean consumable) throws XmlBlasterException {
      if (oid == null || oid.length() == 0)
         throw new XmlBlasterException(glob, ErrorCode.INTERNAL_ILLEGALARGUMENT, ME, "Please supply a valid oid to receive()");

      ContextNode node = ContextNode.valueOf(oid);
      if (node == null) {
         throw new IllegalArgumentException("Can't parse '" + oid + "' to a ContextNode");
      }
      if (node.isOfClass(ContextNode.SESSION_MARKER_TAG))
         node = node.getParent();
      if (node == null) {
         throw new IllegalArgumentException("Can't parse '" + oid + "' to a ContextNode");
      }
      if (node.isOfClass(ContextNode.TOPIC_MARKER_TAG))
         oid = "__cmd:"+oid+"/?historyQueueEntries"; // "__cmd:topic/hello/?historyQueueEntries"
      else if (node.isOfClass(ContextNode.SUBJECT_MARKER_TAG) && node.getChild(ContextNode.SESSION_MARKER_TAG, null) != null)
            oid = "__cmd:"+oid+"/?callbackQueueEntries"; // "__cmd:client/joe/session/1/?callbackQueueEntries";
      else if (node.isOfClass(ContextNode.SUBJECT_MARKER_TAG))
            oid = "__cmd:"+oid+"/?subjectQueueEntries"; // "__cmd:client/joe/?subjectQueueEntries"

      GetKey getKey = new GetKey(glob, oid);
      String qos = "<qos>" +
                   "<querySpec type='QueueQuery'>" +
                   "maxEntries="+maxEntries+"&amp;maxSize=-1&amp;consumable="+consumable+"&amp;waitingDelay="+timeout+
                   "</querySpec>" +
                   "</qos>";
      GetQos getQos = new GetQos(glob, glob.getQueryQosFactory().readObject(qos));
      MsgUnit[] msgs = get(getKey, getQos);
      if (log.isLoggable(Level.FINEST)) log.finest(getLogId()+"Got " + msgs.length + " reply :\n" + ((msgs.length>0)?msgs[0].toXml():""));
      return msgs;
   }

  
   private PublishReturnQos publishSingleChunk(MsgKeyData keyData, MsgQosData chunkQosData, byte[] buf, int length, boolean isLastChunk, long count, Exception ex) throws XmlBlasterException {
      MsgKeyData chunkKeyData = keyData;
      MsgUnit msg = new MsgUnit(chunkKeyData, buf, chunkQosData);
      if (isLastChunk || ex != null)
         chunkQosData.addClientProperty(Constants.addJmsPrefix(XBConnectionMetaData.JMSX_GROUP_EOF, log), true);
      chunkQosData.addClientProperty(Constants.addJmsPrefix(XBConnectionMetaData.JMSX_GROUP_SEQ, log), count);
      if (ex != null)
         msg.getQosData().addClientProperty(Constants.addJmsPrefix(XBConnectionMetaData.JMSX_GROUP_EX, log), ex.getMessage());
      return publish(msg);
   }
  
   public PublishReturnQos[] publishStream(InputStream is, MsgKeyData keyData, MsgQosData qosData, int maxBufSize, I_ReplaceContent contentReplacer) throws XmlBlasterException {
      String streamId = (getGlobal()).getId() + "-" + (new Timestamp()).getTimestamp();
      qosData.addClientProperty(Constants.addJmsPrefix(XBConnectionMetaData.JMSX_GROUP_ID, log), streamId);
      int bufSize = 0;
      String tmpKey = Constants.addJmsPrefix(XBConnectionMetaData.JMSX_MAX_CHUNK_SIZE, log);
      if (qosData.getClientProperty(tmpKey) != null)
         bufSize = qosData.getClientProperty(tmpKey).getIntValue();
      if (bufSize > maxBufSize || bufSize == 0)
         bufSize = maxBufSize;
      long count = 0L;
      PublishReturnQos pubRetQos = null;
      byte[] buf = new byte[bufSize];
      try {
         while (true) {
            buf = new byte[bufSize];
            int offset = 0;
            int remainingLength = bufSize;
            int lengthRead = 0;
            while ((lengthRead = is.read(buf, offset, remainingLength)) != -1) {
               remainingLength -= lengthRead;
               offset += lengthRead;
               if (remainingLength == 0)
                  break;
            }
            int length = offset;
            // cut the buffer if shorter than maximum buffer size
            if (length < buf.length) {
               byte[] tmpBuf = buf;
               buf = new byte[length];
               for (int i=0; i<buf.length;i++)
                  buf[i] = tmpBuf[i];
            }
           
            // We do not need to clone the key since it will not change, but the qos must be cloned
            MsgQosData chunkQosData = (MsgQosData)qosData.clone();
           
            if (contentReplacer != null)
               buf = contentReplacer.replace(buf, chunkQosData.getClientProperties());
            boolean isLastChunk = buf.length < bufSize;
            pubRetQos = publishSingleChunk(keyData, chunkQosData, buf, length, isLastChunk, count, null);
            count++;
            if (length < bufSize)
               return new PublishReturnQos[] { pubRetQos };
         }           
      }
      catch (IOException ex) {
         if (count > 0)
            publishSingleChunk(keyData, qosData, buf, 0, true, count, ex);
         throw new XmlBlasterException(getGlobal(), ErrorCode.RESOURCE, "Sending Chunked message", "failed due to an IOException", ex);
      }
      catch (XmlBlasterException ex) {
         if (count > 0)
            publishSingleChunk(keyData, qosData, buf, 0, true, count, ex);
         throw ex;
      }
   }
  
   public Object getUserObject() {
      return userObject;
   }

   public void setUserObject(Object userObject) {
      this.userObject = userObject;
   }
}
TOP

Related Classes of org.xmlBlaster.client.XmlBlasterAccess

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.