Package org.xmlBlaster.contrib.replication

Source Code of org.xmlBlaster.contrib.replication.ReplSlave

/*------------------------------------------------------------------------------
Name:      ReplSlave.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/

package org.xmlBlaster.contrib.replication;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xmlBlaster.client.I_XmlBlasterAccess;
import org.xmlBlaster.client.key.PublishKey;
import org.xmlBlaster.client.qos.PublishQos;
import org.xmlBlaster.client.qos.SubscribeQos;
import org.xmlBlaster.contrib.ClientPropertiesInfo;
import org.xmlBlaster.contrib.GlobalInfo;
import org.xmlBlaster.contrib.I_Info;
import org.xmlBlaster.contrib.MomEventEngine;
import org.xmlBlaster.contrib.dbwatcher.DbWatcherConstants;
import org.xmlBlaster.contrib.replication.impl.ReplManagerPlugin;
import org.xmlBlaster.engine.admin.I_AdminSession;
import org.xmlBlaster.engine.queuemsg.ReferenceEntry;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.MsgUnit;
import org.xmlBlaster.util.MsgUnitRaw;
import org.xmlBlaster.util.SessionName;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.context.ContextNode;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.def.MethodName;
import org.xmlBlaster.util.def.PriorityEnum;
import org.xmlBlaster.util.dispatch.ConnectionStateEnum;
import org.xmlBlaster.util.qos.ClientProperty;
import org.xmlBlaster.util.qos.ConnectQosData;
import org.xmlBlaster.util.qos.MsgQosData;
import org.xmlBlaster.util.qos.address.Destination;
import org.xmlBlaster.util.queue.I_Entry;
import org.xmlBlaster.util.queue.I_Queue;
import org.xmlBlaster.util.xbformat.MsgInfo;
import org.xmlBlaster.util.xbformat.XmlScriptParser;


/**
* ReplSlave
*
* Used Topics:
* <ul>
*    <li><b>com.avitech-ag.repl.${replName}.data</b><br/>
*        This is the topic used to send the replication data to the slaves.
*    </li>
*    <li><b>com.avitech-ag.repl.${replName}.status</b><br/>
*        This is the topic used to send the replication data to the slaves.
*    </li>
*    <li></li>
* </ul>  

*
* @author <a href="mailto:michele@laghi.eu">Michele Laghi</a>
*/
public class ReplSlave implements I_ReplSlave, ReplSlaveMBean, ReplicationConstants {

   private static Logger log = Logger.getLogger(ReplSlave.class.getName());
   private final static String CONN_STALLED = "stalled";
   private final static String CONN_CONNECTED = "connected";
   private final static String CONN_DISCONNECTED = "disconnected";
  
   private String slaveSessionId;
   private String name;
   private String statusTopic;
   private String dataTopic;
   private Global global;
   boolean initialized;
   private long minReplKey;
   private long maxReplKey;
   private int status;
   private Object mbeanHandle;
   private String sqlResponse;
   private boolean forceSending; // temporary Hack to be removed TODO
   private I_Info persistentInfo;
   private String oldReplKeyPropertyName;
   private String dbWatcherSessionName;
   private ReplManagerPlugin manager;
   private String replPrefix;
   private String replPrefixGroup;
   private String cascadedReplSlave;
   private String cascadedReplPrefix;
   private long forcedCounter;  // counter used when forceSending is set to 'true'
   private String ownVersion;
   private String srcVersion;
   private boolean doTransform;
   private String initialFilesLocation;
   private String lastMessage;
   private String lastDispatcherException = "";

   private boolean dispatcherActive;
   private long queueEntries;
   private boolean connected;
   private String sessionName = "";
   private long[] transactionSeq;
   private long messageSeq;
   private long transactionSeqVisible;
   /** These properties are used to transport the information from the check to the postCheck method. */
   private int tmpStatus;
   private String lastMessageKey;
   private long maxChunkSize = 1024L*1024; // TODO make this configurable
   private String masterConn = CONN_DISCONNECTED;
  
   /** we don't want to sync the check method because the jmx will synchronize on the object too */
   private Object initSync = new Object();
  
   /** The queue associated to this slave. It is associated on first invocation of check */
   private I_Queue queue;
   private boolean stalled;
   /** used for monitoring: to know how many entries are ptp (normally initial updates) */
   private long ptpQueueEntries;
   private String initialDataTopic;
   /** The real amount of entries in the cb queue (not calculated) */
   private long cbQueueEntries;
   private boolean countSingleMessages;
   private int maxNumOfEntries;
   private String maxNumOfEntriesKey;

   public ReplSlave(Global global, ReplManagerPlugin manager, String slaveSessionId, ConnectQosData connQosData) throws XmlBlasterException {
      this.forcedCounter = 0L;
      this.global = global;
      this.manager = manager;
      this.slaveSessionId = slaveSessionId;
      // this.status = STATUS_UNUSED;
      // setStatus(STATUS_NORMAL);
      this.status = STATUS_UNCONFIGURED;
      this.lastMessage = "";
      //final boolean doPersist = false;
      //final boolean dispatcherActive = false;
      this.lastMessageKey = this.slaveSessionId + ".lastMessage";
      try {
         //setDispatcher(dispatcherActive, doPersist);
         this.persistentInfo = this.manager.getPersistentInfo();
         this.lastMessage = this.persistentInfo.get(this.lastMessageKey, "");
        
         maxNumOfEntries = connQosData.getClientProperty(REPLICATION_MAX_ENTRIES_KEY, 0);
         maxNumOfEntriesKey = REPLICATION_MAX_ENTRIES_KEY + "." + slaveSessionId;
         int tmp = persistentInfo.getInt(maxNumOfEntriesKey, 0);
         if (maxNumOfEntries != tmp) {
            log.info("The ConnectQos Property '" + REPLICATION_MAX_ENTRIES_KEY + "' for replication slave '" + slaveSessionId + "' was initially set to '" + maxNumOfEntries + "' but has explicitly been changed with jmx to '" + tmp + "'");
            maxNumOfEntries = tmp;
         }
      }
      catch (Exception ex) {
         throw new XmlBlasterException(this.global, ErrorCode.RESOURCE, "ReplSlave constructor", "could not instantiate correctly", ex);
      }
   }

   public String getTopic() {
      return this.dataTopic;
   }
  
   public long getMinReplKey() {
      if (this.forceSending)
         return this.forcedCounter;
      return this.minReplKey;
   }
  
   public long getMaxReplKey() {
      if (this.forceSending)
         return this.forcedCounter;
      return this.maxReplKey;
   }

   public int getStatusAsInt() {
      return this.status;
   }
  
   public String getStatus() {
      switch (this.status) {
         case STATUS_INITIAL : return "INITIAL";
         case STATUS_TRANSITION : return "TRANSITION";
         case STATUS_INCONSISTENT : return "INCONSISTENT";
         case STATUS_UNCONFIGURED : return "UNCONFIGURED";
         default : return "NORMAL";
      }
   }

   /**
    * The info comes as the client properties of the subscription Qos. Avoids double configuration.
    */
   public void init(I_Info info) throws Exception {
      synchronized(this.initSync) {
         // we currently allow re-initialize since we can serve several dbWatchers for one DbWriter
         this.replPrefix = info.get("_replName", null);
         if (this.replPrefix == null)
            throw new Exception("The replication name '_replName' has not been defined");
         this.replPrefixGroup = info.get(REPL_PREFIX_GROUP_KEY, this.replPrefix);
         this.name = "replSlave" + this.replPrefix + slaveSessionId;
         this.dataTopic = info.get(DbWatcherConstants.MOM_TOPIC_NAME, "replication." + this.replPrefix);
         // only send status messages if it has been configured that way
         this.statusTopic = info.get(DbWatcherConstants.MOM_STATUS_TOPIC_NAME, null);
        
         // TODO Remove this when a better solution is found : several ReplSlaves for same Writer if data comes from several DbWatchers.
         boolean forceSend = info.getBoolean(REPLICATION_FORCE_SENDING, false);
         if (forceSend)
            this.forceSending = true;
         String instanceName = this.manager.getInstanceName() + ContextNode.SEP + this.slaveSessionId;
         ContextNode contextNode = new ContextNode(ContextNode.CONTRIB_MARKER_TAG, instanceName,
               this.global.getContextNode());
         this.mbeanHandle = this.global.registerMBean(contextNode, this);
        
         this.dbWatcherSessionName = info.get(this.slaveSessionId + DBWATCHER_SESSION_NAME, null);
         this.cascadedReplPrefix = this.persistentInfo.get(this.slaveSessionId + CASCADED_REPL_PREFIX, null);
         this.cascadedReplSlave = this.persistentInfo.get(this.slaveSessionId + CASCADED_REPL_SLAVE, null);
         log.info(name + ": associated DbWatcher='" + this.dbWatcherSessionName + "' cascaded replication prefix='" + this.cascadedReplPrefix + "' and cascaded repl. slave='" + this.cascadedReplSlave + "'");
         int tmp = this.persistentInfo.getInt(this.slaveSessionId + ".status", -1);
         if (tmp > -1)
            setStatus(tmp);
        
         final boolean doPersist = false;
         setDispatcher(this.persistentInfo.getBoolean(this.slaveSessionId + ".dispatcher", false), doPersist);

         this.oldReplKeyPropertyName = this.slaveSessionId + ".oldReplData";

         initTransactionSequenceIfNeeded(null);
        
         this.srcVersion = info.get(REPLICATION_VERSION, "0.0");
         this.ownVersion = info.get(REPL_VERSION, null);
        
         if (this.ownVersion != null) {
            this.persistentInfo.put(this.slaveSessionId + "." + ReplicationConstants.REPL_VERSION, this.ownVersion);
         }
         else {
            this.ownVersion = this.persistentInfo.get(this.slaveSessionId + "." + ReplicationConstants.REPL_VERSION, this.srcVersion);
         }
        
         if (this.srcVersion != null && this.ownVersion != null && !this.srcVersion.equalsIgnoreCase(this.ownVersion))
            this.doTransform = true;

         initialFilesLocation = info.get(ReplicationConstants.INITIAL_FILES_LOCATION, null);
         initialDataTopic = info.get("replication.initialDataTopic", "replication.initialData");
         countSingleMessages = info.getBoolean("replication.countSingleMsg", false);
         this.initialized = true;
      }
   }

   /**
    * This method is needed since in some cases writing operations on the counters can occur before the init
    * method has been invoked.
    * @param warnText if null no warning will be written, otherwise the specified text will be output as a warning.
    *
    */
   private void initTransactionSequenceIfNeeded(String warnText) {
      if (this.transactionSeq != null)
         return;
      if (warnText != null) {
         log.warning(warnText);
         if (log.isLoggable(Level.FINE))
            log.fine(Global.getStackTraceAsString(null));
      }
      synchronized(this.initSync) {
         this.transactionSeq = new long[PriorityEnum.MAX_PRIORITY.getInt()+1]; // 10 priorities [0..9]
         long[] replData = ReplManagerPlugin.readOldReplData(this.persistentInfo, this.oldReplKeyPropertyName);
         if (replData.length < 5) { // Old Style: REMOVE THIS LATER !!!!
            this.maxReplKey = replData[0];
            this.minReplKey = replData[3];
            for (int i=0; i < this.transactionSeq.length; i++)
               this.transactionSeq[i] = replData[1];
            this.transactionSeqVisible = this.transactionSeq[5];
            this.messageSeq = replData[2];
            this.ptpQueueEntries = 0L;
         }
         else { // NEW STYLE
            this.maxReplKey = replData[0];
            this.minReplKey = replData[1];
            this.messageSeq = replData[2];
            this.ptpQueueEntries = replData[3];
            for (int i=0; i < this.transactionSeq.length; i++)
               this.transactionSeq[i] = replData[i+4];
            this.transactionSeqVisible = this.transactionSeq[5];
         }
      }
   }
  
   private final void setStatus(int status) {
      boolean doStore = status != this.status;
      this.status = status;
      if (this.persistentInfo != null && doStore) { // can also be called before init is called.
         if (this.status != STATUS_UNCONFIGURED)
            this.persistentInfo.put(this.slaveSessionId + ".status", "" + status);
      }
      // this is a temporary solution for the monitoring
      String client = "client/";
      int pos = this.slaveSessionId.indexOf(client);
      if (pos < 0)
         log.warning("session name '" + this.slaveSessionId + "' does not start with '" + client + "'");
      else {
         String key = "__" + this.slaveSessionId.substring(pos + client.length());
         org.xmlBlaster.engine.ServerScope engineGlob = this.getEngineGlobal(this.global);
         if (engineGlob == null)
            log.warning("Can not write status since no engine global found");
         else {
            log.info("setting property '" + key + "' to '" + getStatus());
            engineGlob.getProperty().getProperties().setProperty(key, getStatus());
         }
      }
   }
  
   /**
    * Note that the transKey shall not be the transactionSeq instance otherwise it will never detect a change
    * @param replKey
    * @param transKey
    * @param msgKey
    * @param minReplKey
    */
   private final void setMaxReplKey(long replKey, long[] transKey, long msgKey, long minReplKey, long ptpQueueEntries) {
      if (replKey > this.maxReplKey)
         this.maxReplKey = replKey;
      if (minReplKey > this.minReplKey)
         this.minReplKey = minReplKey;
      if (msgKey > this.messageSeq)
         this.messageSeq = msgKey;
      this.ptpQueueEntries = ptpQueueEntries;
      long[] data = new long[this.transactionSeq.length+4];
      data[0] = replKey;
      data[1] = minReplKey;
      data[2] = msgKey;
      data[3] = ptpQueueEntries;
      for (int i=0; i < transKey.length; i++)
         data[i+4] = transKey[i];
      ReplManagerPlugin.storeReplData(this.persistentInfo, this.oldReplKeyPropertyName, data);

      String client = "client/";
      if (this.slaveSessionId == null)
         return;
      int pos = this.slaveSessionId.indexOf(client);
      if (pos < 0)
         log.warning("session name '" + this.slaveSessionId + "' does not start with '" + client + "'");
      else {
         String key = "__" + this.slaveSessionId.substring(pos + client.length()) + "_MaxReplKey";
         org.xmlBlaster.engine.ServerScope engineGlob = this.getEngineGlobal(this.global);
         if (engineGlob == null)
            log.warning("Can not write status since no engine global found");
         else {
            log.finest("setting property '" + key + "' to '" + getMaxReplKey());
            engineGlob.getProperty().getProperties().setProperty(key, String.valueOf(getMaxReplKey()));
         }
      }
   }

   public boolean reInitiate(I_Info info) throws Exception {
      final boolean onlyRegister = true;
      return run(info, this.dbWatcherSessionName, this.cascadedReplPrefix, this.cascadedReplSlave, onlyRegister);
   }
  
   /**
    *
    * @param info
    * @param dbWatcherSessionId
    * @param cascadeReplPrefix
    * @param cascadeSlaveSessionName
    * @param onlyRegister if true it only registers for initial update but does not execute it yet.
    * It will wait for a further (common) start message.
    * @return
    * @throws Exception
    */
   public boolean run(I_Info info, String dbWatcherSessionId, String cascadeReplPrefix, String cascadeSlaveSessionName, boolean onlyRegister) throws Exception {
      if (this.status != STATUS_NORMAL && this.status != STATUS_INCONSISTENT && this.status != STATUS_UNCONFIGURED) {
         log.warning("will not start initial update request since one already ongoing for '" + this.name + "'");
         return false;
      }
      this.persistentInfo.put(this.slaveSessionId + CASCADED_REPL_PREFIX, cascadeReplPrefix);
      this.persistentInfo.put(this.slaveSessionId + CASCADED_REPL_SLAVE, cascadeSlaveSessionName);
     
      info.put(this.slaveSessionId + DBWATCHER_SESSION_NAME, dbWatcherSessionId);
      init(info);
      prepareForRequest(info);
      requestInitialData(dbWatcherSessionId, onlyRegister);
      return true;
   }
  
   /**
    * This is the first step in the process of requesting the initial Data.
    * <ul>
    *   <li>It clears the callback queue of the real slave</li>
    *   <li>It sends a message to the real slave to inform him that
    *       a new initial update has been initiated. This is a PtP
    *       message with a well defined topic, so administrators can
    *       subscribe to it too.
    *   </li>
    *   <li>It then deactivates the callback dispatcher of the real slave</li>
    *   <li>makes a persistent subscription on behalf of the real slave
    *       by passing as a mime access filter an identifier for himself.
    *   </li>
    * </ul>
    * @see org.xmlBlaster.contrib.replication.I_ReplSlave#prepareForRequest(I_Info)
    */
   public void prepareForRequest(I_Info individualInfo) throws Exception {
      if (!this.initialized)
         throw new Exception("prepareForRequest: '" + this.name + "' has not been initialized properly or is already shutdown, check your logs");
      log.info("prepareForRequest");
      long clearedMsg = clearQueueSync();
      log.info("clearing of callback queue before initiating: '" + clearedMsg + "' where removed since obsolete");

      if (this.statusTopic != null)
         sendStatusInformation("dbInitStart");
      final boolean doPersist = true;
      doPause(doPersist); // stop the dispatcher
     
      I_AdminSession session = getSession();
      // first unsubscribe (in case it did already an initial update previously, this is needed to remove the subscription
      // (and thereby its outdate subscription qos from persistence). On a back replication, i.e. where you have more than
      // one sources you don't want to do this.
      if (individualInfo.getBoolean("replication.forceNewSubscription", true)) {
         try {
            session.unSubscribe(this.dataTopic, "");
         }
         catch (Throwable ex) {
         }
      }
     
      SubscribeQos subQos = new SubscribeQos(this.global);
      subQos.setMultiSubscribe(false);
      subQos.setWantInitialUpdate(false);
      subQos.setPersistent(true);
      // this fills the client properties with the contents of the individualInfo object.
      new ClientPropertiesInfo(subQos.getData().getClientProperties(), individualInfo);
      session.subscribe(this.dataTopic, subQos.toXml());
      synchronized(this.initSync) {
         setStatus(STATUS_INITIAL);
      }
   }
  
   private void sendStatusInformation(String status) throws Exception {
      log.info("send status information '" + status + "'");
      I_XmlBlasterAccess conn = this.global.getXmlBlasterAccess();
      PublishKey pubKey = new PublishKey(this.global, this.statusTopic);
      Destination destination = new Destination(new SessionName(this.global, this.slaveSessionId));
      destination.forceQueuing(true);
      PublishQos pubQos = new PublishQos(this.global, destination);
      pubQos.setPersistent(true);
      MsgUnit msg = new MsgUnit(pubKey, status.getBytes(), pubQos);
      conn.publish(msg);
   }
  
   /**
    * Sends a PtP message to the responsible for the initial update (which is the
    * DbWatcher or an object running in the DbWatcher jvm) telling a new initial
    * update has to be initiating.
    *
    * @see org.xmlBlaster.contrib.replication.I_ReplSlave#requestInitialData()
    */
   public void requestInitialData(String dbWatcherSessionId, boolean onlyRegister) throws Exception {
      log.info(this.name + " sends now an initial update request to the Master '" + dbWatcherSessionId + "'");
      I_XmlBlasterAccess conn = this.global.getXmlBlasterAccess();
      // no oid for this ptp message
      PublishKey pubKey = new PublishKey(this.global, REQUEST_INITIAL_DATA_TOPIC);
      Destination destination = new Destination(new SessionName(this.global, dbWatcherSessionId));
      destination.forceQueuing(true);
      PublishQos pubQos = new PublishQos(this.global, destination);
      pubQos.addClientProperty(ReplicationConstants.SLAVE_NAME, this.slaveSessionId);
      pubQos.addClientProperty(ReplicationConstants.REPL_VERSION, this.ownVersion);
      if (this.initialFilesLocation != null)
         pubQos.addClientProperty(ReplicationConstants.INITIAL_FILES_LOCATION, this.initialFilesLocation);
      pubQos.setPersistent(true);
      if (onlyRegister)
         pubQos.addClientProperty(ReplicationConstants.INITIAL_UPDATE_ONLY_REGISTER, onlyRegister);
      MsgUnit msg = new MsgUnit(pubKey, ReplicationConstants.REPL_REQUEST_UPDATE.getBytes(), pubQos);
      conn.publish(msg);
   }

   private org.xmlBlaster.engine.ServerScope getEngineGlobal(Global glob) {
      return (org.xmlBlaster.engine.ServerScope)glob.getObjectEntry(GlobalInfo.ORIGINAL_ENGINE_GLOBAL);
   }
  
   private I_AdminSession getSession() throws Exception {
      return this.manager.getSession(this.slaveSessionId);
   }
  
   /**
    * @see org.xmlBlaster.contrib.replication.I_ReplSlave#reactivateDestination()
    */
   public void reactivateDestination(long minReplKey, long maxReplKey) throws Exception {
      synchronized(this.initSync) {
         log.info("Initial Operation completed with replication key interval [" + minReplKey + "," + maxReplKey + "]");
         if (!this.initialized)
            throw new Exception("prepareForRequest: '" + this.name + "' has not been initialized properly or is already shutdown, check your logs");

         if (STATUS_INCONSISTENT == this.status) {
            log.warning("Will not change the status to transition since the initialUpdate has been cancelled");
            return;
         }
           
         this.minReplKey = minReplKey;
         this.maxReplKey = maxReplKey;
         setStatus(STATUS_TRANSITION);
         final boolean doPersist = true;
         doContinue(doPersist);
      }
   }

   /**
    * @see org.xmlBlaster.contrib.dbwriter.I_ContribPlugin#shutdown()
    */
   public void shutdown() {
      synchronized (this.initSync) {
         if (!this.initialized)
            return;
         this.global.unregisterMBean(this.mbeanHandle);
         this.initialized = false;
      }
   }


   private final void doTransform(MsgUnit msgUnit) throws Exception {
      if (this.doTransform) {
         // ClientProperty prop = msgUnit.getQosData().getClientProperty(ReplicationConstants.DUMP_ACTION);
         // if (prop == null) {
         if (msgUnit.getContentMime() != null && msgUnit.getContentMime().equals("text/xml")) {
            byte[] content = msgUnit.getContent();
            InputStream is = MomEventEngine.decompress(new ByteArrayInputStream(content), msgUnit.getQosData().getClientProperties());
            content = ReplManagerPlugin.getContent(is);
            content = this.manager.transformVersion(this.replPrefix, this.ownVersion, this.slaveSessionId, content);
            msgUnit.setContent(content);
         }
      }
   }
  
   /**
    * Returns the name of the directory where the entries have been stored.
    * @param entry The entry to add as a chunk.
    * @param location The location where to add it.
    * @param subDirProp
    * @return
    * @throws Exception
    */
   private String storeChunkLocally(ReferenceEntry entry, ClientProperty location, ClientProperty subDirProp) throws Exception {
      if (entry == null)
         throw new Exception("The entry to store is null, can not store");
      MsgUnit msgUnit = entry.getMsgUnit();
      if (msgUnit == null)
         throw new Exception("The msgUnit to store is null, can not store");
      if (location == null || location.getStringValue() == null || location.getStringValue().trim().length() < 1)
         throw new Exception("The location is empty, can not store the message unit '" + msgUnit.getLogId() + "'");
      // String fileId = "" + new Timestamp().getTimestamp();
      // this way they are automatically sorted and in case of a repeated write it simply would be overwritten.
      String fileId = entry.getPriority() + "-" + entry.getUniqueId();
     
      String pathName = location.getStringValue().trim();
      File dirWhereToStore = ReplManagerPlugin.checkExistance(pathName);
     
      if (subDirProp == null)
         throw new Exception("The property to define the file name (dataId) is not set, can not continue");
      String subDirName = subDirProp.getStringValue();
      if (subDirName == null || subDirName.trim().length() < 1)
         throw new Exception("The subdirectory to be used to store the initial data is empty");
      File subDir = new File(dirWhereToStore, subDirName);
      String completeSubdirName = subDir.getAbsolutePath();
      if (!subDir.exists()) {
         if (!subDir.mkdir()) {
            String txt = "could not make '" + completeSubdirName + "' to be a directory. Check your rights";
            log.severe(txt);
            throw new Exception(txt);
         }
      }
     
      File file = new File(subDir, fileId);
      if (file.exists())
         log.warning("File '" + file.getAbsolutePath() + "' exists already. Will overwrite it");
      FileOutputStream fos = new FileOutputStream(file);
      MsgUnitRaw msgUnitRaw = new MsgUnitRaw(msgUnit.getKey(), msgUnit.getContent(), msgUnit.getQos());
      MsgInfo msgInfo = new MsgInfo(this.global, MsgInfo.INVOKE_BYTE, MethodName.UPDATE_ONEWAY, this.slaveSessionId);
      msgInfo.addMessage(msgUnitRaw);
      XmlScriptParser parser = new XmlScriptParser();
      parser.init(new Global(), null, null);
      fos.write(parser.toLiteral(msgInfo).getBytes());
      fos.close();
      log.info("MsgUnit '" + msgUnit.getQosData().getRcvTimestamp().getTimestamp() + "' has been written to file '" + file.getAbsolutePath() + "'");
      return completeSubdirName;
   }
  
   /**
    *
    * @param newMsg If newMsg is null, it cleans the message otherwise the behaviour depens on doAdd
    * @param doAdd if true, the message is added to the current message, if false it is replaced.
    */
   private void changeLastMessage(String newMsg) {
      log.info("monitor message: '" + newMsg + "' invoked");
      if (newMsg == null) {
         if (this.lastMessage != null && this.lastMessage.length() > 0)
            this.lastMessage = "";
         this.persistentInfo.put(this.lastMessageKey, null);
      }
      else {
         this.lastMessage = newMsg.trim();
         this.persistentInfo.put(this.lastMessageKey, this.lastMessage);
      }
   }
  
   /*
   private void calculateCounters(MsgQueueEntry[] entries) throws XmlBlasterException {
      if (entries.length > 0) {
         for (int i=entries.length-1; i > -1; i--) {
            ReferenceEntry entry = (ReferenceEntry)entries[i];
            if (log.isLoggable(Level.FINEST)) {
               String txt = new String(decompressQueueEntryContent(entry));
               log.finest("Processing entry '" + txt + "' for client '"  + this.name + "'");
            }
            MsgUnit msgUnit = entry.getMsgUnit();
            long tmpCounter = this.tmpTransSeq + msgUnit.getQosData().getClientProperty(ReplicationConstants.NUM_OF_TRANSACTIONS, 1L);
            //long tmpCounter = msgUnit.getQosData().getClientProperty(ReplicationConstants.TRANSACTION_SEQ, 0L);
            if (tmpCounter != 0L)
               this.tmpTransSeq = tmpCounter;
            this.tmpReplKey = msgUnit.getQosData().getClientProperty(ReplicationConstants.REPL_KEY_ATTR, -1L);
            tmpCounter = msgUnit.getQosData().getClientProperty(ReplicationConstants.MESSAGE_SEQ, 0L);
            if (tmpCounter != 0L)
               this.tmpMsgSeq =  tmpCounter;
            if (this.tmpReplKey > -1L) {
               break; // the other messages will have lower numbers (if any) so we break for performance.
            }
         }     
      }
   }
   */
  
   /**
    *
    */
   public ArrayList check(List<I_Entry> entries, I_Queue queue) throws Exception {
      this.queue = queue;
      synchronized (this.initSync) {
         this.tmpStatus = -1;
         this.forcedCounter++;
         log.info("check invoked with status '" + getStatus() + "' for client '" + this.slaveSessionId + "' (invocation since start is '" + this.forcedCounter + "')");
         if (!this.initialized) {
            log.warning("check invoked without having been initialized. Will repeat operation until the real client connects");
            Thread.sleep(250L); // to avoid too fast looping
            return new ArrayList();
         }
         if (this.status == STATUS_INITIAL && !this.forceSending) { // should not happen since Dispatcher is set to false
            log.warning("check invoked in INITIAL STATUS. Will stop the dispatcher");
            final boolean doPersist = true;
            doPause(doPersist);
            return new ArrayList();
         }

         // if (entries != null && entries.size() > 1)
         //    log.severe("the entries are '" + entries.size() + "' but we currently only can process one single entry at a time");
        
         // check if already processed ... and at the same time do the versioning transformation (if needed)
         for (int i=entries.size()-1; i > -1; i--) {
            ReferenceEntry entry = (ReferenceEntry)entries.get(i);
            MsgUnit msgUnit = entry.getMsgUnit();
            ClientProperty alreadyProcessed = msgUnit.getQosData().getClientProperty(ReplicationConstants.ALREADY_PROCESSED_ATTR);
            if (alreadyProcessed != null) {
               log.warning("Received entry for client '" + this.slaveSessionId + "' which was already processed. Will remove it");
               queue.removeRandom(entry);
               entries.remove(i);
            }
            else
               doTransform(msgUnit);
         }
        
         // check if one of the messages is the transition end tag, also check if the total size is exceeded
         ArrayList remoteEntries = new ArrayList();
         long totalSize = 0L;
         for (int i=0; i < entries.size(); i++) {
            ReferenceEntry entry = (ReferenceEntry)entries.get(i);
            MsgUnit msgUnit = entry.getMsgUnit();
            ClientProperty endMsg = msgUnit.getQosData().getClientProperty(ReplicationConstants.END_OF_TRANSITION);
           
            // check if the message is the end of the data (only sent in case the initial data has to be stored on
            // file in which case the dispatcher shall return in its waiting state.
            ClientProperty endOfData = msgUnit.getQosData().getClientProperty(ReplicationConstants.INITIAL_DATA_END);
            ClientProperty initialFilesLocation = msgUnit.getQosData().getClientProperty(ReplicationConstants.INITIAL_FILES_LOCATION);
            ClientProperty subDirName = msgUnit.getQosData().getClientProperty(ReplicationConstants.INITIAL_DATA_ID);
            if (endOfData != null) {
               final boolean doPersist = true;
               doPause(doPersist);
               queue.removeRandom(entry);
               // entries.remove(i); // endOfData will be kept locally, not sent to slave
               String dirName = "unknown";
               if (subDirName != null) {
                  if (initialFilesLocation != null) {
                     File base = new File(initialFilesLocation.getStringValue().trim());
                     File complete = new File(base, subDirName.getStringValue().trim());
                     dirName = complete.getAbsolutePath();
                  }
               }
               changeLastMessage("Manual Data transfer: WAITING (stored on '" + dirName + "')");
               break; // we need to interrupt here: all subsequent entries will be processed later.
            }

            // check if the message has to be stored locally
            ClientProperty endToRemote = msgUnit.getQosData().getClientProperty(ReplicationConstants.INITIAL_DATA_END_TO_REMOTE);
            if (initialFilesLocation != null && (endToRemote == null || !endToRemote.getBooleanValue()) && (endMsg == null || !endMsg.getBooleanValue())) {
               storeChunkLocally(entry, initialFilesLocation, subDirName);
               queue.removeRandom(entry);
               // entries.remove(i);
               continue;
            }
           
            if (endMsg != null) {
               log.info("Received msg marking the end of the initial for client '" + this.slaveSessionId + "' update: '" + this.name + "' going into NORMAL operations");
               startCascadedAndChangeStatus();
            }
            byte[] content = msgUnit.getContent();
            if (content != null)
               totalSize += content.length;
            if (totalSize <= this.maxChunkSize || i == 0)
               remoteEntries.add(entry);
            else
               break;
         }
         entries = null; // we can free it here since not needed anymore
        
         if (this.status == STATUS_NORMAL || this.status == STATUS_INCONSISTENT || this.status == STATUS_UNCONFIGURED)
            return remoteEntries;
        
         ArrayList ret = new ArrayList();
         for (int i=0; i < remoteEntries.size(); i++) {
            ReferenceEntry entry = (ReferenceEntry)remoteEntries.get(i);
            MsgUnit msgUnit = entry.getMsgUnit();
            long replKey = msgUnit.getQosData().getClientProperty(ReplicationConstants.REPL_KEY_ATTR, -1L);
            /* this is done when acknowledge comes
            if (replKey > -1L) {
               setMaxReplKey(replKey, this.tmpTransSeq, this.tmpMsgSeq);
            }
            */
            log.info("check: processing '" + replKey + "' for client '" + this.slaveSessionId + "' ");
            if (replKey < 0L) { // this does not come from the normal replication, so these are other messages which we just deliver
               ClientProperty endMsg = msgUnit.getQosData().getClientProperty(ReplicationConstants.END_OF_TRANSITION);
               if (endMsg == null) {
                  log.warning("the message unit with qos='" + msgUnit.getQosData().toXml() + "' and key '" + msgUnit.getKey() + "'  for client '" + this.slaveSessionId + "' has no 'replKey' Attribute defined.");
                  ret.add(entry);
                  continue;
               }
            }
            log.info("repl entry '" + replKey + "' for range [" + this.minReplKey + "," + this.maxReplKey + "] for client '" + this.slaveSessionId + "' ");
            if (replKey >= this.minReplKey || this.forceSending) {
               log.info("repl adding the entry for client '" + this.slaveSessionId + "' ");
               doTransform(msgUnit);
               ret.add(entry);
               /* TODO TEMPORARLY REMOVED FOR TESTING: also test no initial dump and manual transfer
               if (replKey > this.maxReplKey || this.forceSending) {
                  log.info("entry with replKey='" + replKey + "' is higher than maxReplKey)='" + this.maxReplKey + "' switching to normal operation again for client '" + this.slaveSessionId + "' ");
                  startCascadedAndChangeStatus();
               }
               */
            }
            else { // such messages have been already from the initial update. (obsolete messages are removed)
               log.info("removing entry with replKey='" + replKey + "' since older than minEntry='" + this.minReplKey + "' for client '" + this.slaveSessionId + "' ");
               queue.removeRandom(entry);
            }
         }
        
         // check if there are more than one entry the keep-transaction-flag has to be set:
         if (ret.size() > 1) {
            for (int i=0; i < ret.size()-1; i++) {
               ReferenceEntry entry = (ReferenceEntry)entries.get(i);
               MsgUnit msgUnit = entry.getMsgUnit();
               msgUnit.getQosData().addClientProperty(KEEP_TRANSACTION_OPEN, true);
            }
            log.info("Sending '" + ret.size() + "' entries in one single message");
         }
         return ret;
      }
   }

   private final void startCascadedAndChangeStatus() throws Exception {
      if (this.cascadedReplPrefix != null && this.cascadedReplSlave != null && this.cascadedReplPrefix.trim().length() > 0 && this.cascadedReplSlave.trim().length() > 0) {
         log.info("initiating the cascaded replication with replication.prefix='" + this.cascadedReplPrefix + "' for slave='" + this.cascadedReplSlave + "'");
         this.manager.initiateReplicationNonMBean(this.cascadedReplSlave, this.cascadedReplPrefix, null, null, null);
      }
      else {
         log.info("will not cascade initiation of any further replication for '" + this.name + "' since no cascading defined");
      }
      setStatus(STATUS_NORMAL);
   }
  
  
   /**
    * @return Returns the sqlResponse.
    */
   public String getSqlResponse() {
      return this.sqlResponse;
   }

  
   /**
    * @param sqlResponse The sqlResponse to set.
    */
   public void setSqlResponse(String sqlResponse) {
      this.sqlResponse = sqlResponse;
   }

   /**
    * @see org.xmlBlaster.contrib.I_ContribPlugin#getUsedPropertyKeys()
    */
   public Set getUsedPropertyKeys() {
      return new HashSet();
   }

   public boolean setDispatcher(boolean status) {
      try {
         if (!status)
            changeLastMessage("DISPATCHER STOPPED MANUALLY BY ADMIN");
         setDispatcher(status, true);
         return true;
      }
      catch (Exception ex) {
         log.severe("Exception occured when trying to set the dispatcher to '" + status + "': " + ex.getMessage());
         ex.printStackTrace();
         return false;
      }
   }
  
   public final boolean setDispatcher(boolean status, boolean doPersist) throws Exception {
      I_AdminSession session = getSession();
      session.setDispatcherActive(status);
      if (doPersist)
         this.persistentInfo.put(this.slaveSessionId + ".dispatcher", "" + status);
      // to speed up refresh on monitor
      this.dispatcherActive = session.getDispatcherActive();
      if (this.dispatcherActive)
         changeLastMessage(null); // clear the exceptions (and last messages)
      return this.dispatcherActive;
   }
  
   /**
    * @see org.xmlBlaster.contrib.replication.ReplSlaveMBean#doContinue()
    */
   public void doContinue(boolean doPersist) throws Exception {
      setDispatcher(true, doPersist);
   }

   /**
    * @see org.xmlBlaster.contrib.replication.ReplSlaveMBean#doPause()
    */
   public void doPause(boolean doPersist) throws Exception {
      setDispatcher(false, doPersist);
   }
  
   public void handleException(Throwable ex) {
      try {
         if (ex instanceof XmlBlasterException) {
            XmlBlasterException xmlblEx = ((XmlBlasterException)ex);
            log.warning(xmlblEx.toXml());
            if (xmlblEx.getEmbeddedException() != null)
               changeLastMessage(xmlblEx.getEmbeddedMessage());
            else
               changeLastMessage(ex.getMessage());
         }
         else
            changeLastMessage(ex.getMessage());
         final boolean doPersist = true;
         doPause(doPersist);
      }
      catch (Throwable e) {
         log.severe("An exception occured when trying to pause the connection: " + e.getMessage());
         ex.printStackTrace();
      }
   }
  
   /**
    * Toggles the dispatcher from active to inactive or vice versa.
    * Returns the actual state.
    * @see org.xmlBlaster.contrib.replication.ReplSlaveMBean#toggleActive()
    * @return the actual state.
    */
   public boolean toggleActive() throws Exception {
      synchronized(this.initSync) {
         I_AdminSession session = getSession();
         final boolean doPersist = true;
         boolean ret = setDispatcher(!session.getDispatcherActive(), doPersist);
         if (!ret)
            changeLastMessage("Dispatcher stopped manually by Admin");
         return ret;
      }
   }
  
   /**
    * TODO fix this since it potentially could delete request from other slaves since the DbWatcher is serving
    * several slaves.
    * Cancels an ongoing initialUpdate Request.
    */
   public void cancelInitialUpdate(boolean async) throws Exception {
      if (this.status == STATUS_NORMAL)
         return;
      if (!this.initialized)
         throw new Exception("cancelInitialUpdate: '" + this.name + "' has not been initialized properly or is already shutdown, check your logs");
      if (this.dbWatcherSessionName == null)
         throw new Exception("The DbWatcher Session Id is null, can not cancel");

      if (async) {
         (new Thread() {
            public void run() {
               cancelUpdateAsyncPart();
            }
         }).start();
      }
      else
         cancelUpdateAsyncPart();
   }

   /**
    * TODO fix this since it potentially could delete request from other slaves since the DbWatcher is serving
    * several slaves.
    * Cancels an ongoing initialUpdate Request.
    */
   public void cancelInitialUpdate() throws Exception {
      cancelInitialUpdate(true);
   }

   /**
    * The cancelUpdate is invoked asynchronously to avoid log blocking of the monitor
    * when the cancel operation is going on.
    */
   private void cancelUpdateAsyncPart() {
      try {
         I_AdminSession session = getSession();
         long clearedMsg = session.clearCallbackQueue();
         log.info("clearing of callback queue: '" + clearedMsg + "' where removed since a cancel request was done");

         // sending the cancel op to the DbWatcher
         log.info(this.name + " sends now a cancel request to the Master '" + this.dbWatcherSessionName + "'");
         I_XmlBlasterAccess conn = this.global.getXmlBlasterAccess();
         // no oid for this ptp message
         PublishKey pubKey = new PublishKey(this.global, REQUEST_CANCEL_UPDATE_TOPIC);
         Destination destination = new Destination(new SessionName(this.global, this.dbWatcherSessionName));
         destination.forceQueuing(true);
         PublishQos pubQos = new PublishQos(this.global, destination);
         pubQos.addClientProperty(ReplicationConstants.SLAVE_NAME, this.slaveSessionId);
         pubQos.setPersistent(false);
         MsgUnit msg = new MsgUnit(pubKey, ReplicationConstants.REPL_REQUEST_CANCEL_UPDATE.getBytes(), pubQos);
         conn.publish(msg);
         // TODO Check this since it could mess up the current status if one is exaclty finished now
         //setStatus(STATUS_NORMAL);
         setStatus(STATUS_INCONSISTENT);
         changeLastMessage("Initial Update interrupted by the ADMIN");
      }
      catch (Exception ex) {
         log.severe("An exception occured when trying to cancel the initial update for '" + this.replPrefix + "'");
         ex.printStackTrace();
      }
   }
  
   private long clearQueueSync() {
      long ret = 0L;
      initTransactionSequenceIfNeeded("clearQueueSync has been invoked before init");
      try {
         ret = getSession().clearCallbackQueue();
         transactionSeq = (long[])manager.getCurrentTransactionCount(replPrefix).clone();
         ptpQueueEntries = 0L;
         setMaxReplKey(maxReplKey, transactionSeq, messageSeq, minReplKey, ptpQueueEntries);
      }
      catch (Exception ex) {
         ex.printStackTrace();
      }
      return ret;
   }
  
   public void clearQueue() throws Exception {
      long ret = getSession().getCbQueueNumMsgs();
      if (ret > 0) {
         setStatus(STATUS_INCONSISTENT);
         log.warning("has been invoked");
         (new Thread() {
            public void run() {
               clearQueueSync();
            }
         }).start();
      }
   }

   public long removeQueueEntries(long entries) throws Exception {
      log.warning("has been invoked with entries='" + entries + "'");
      long ret = getSession().removeFromCallbackQueue(entries);
      if (ret > 0)
         setStatus(STATUS_INCONSISTENT);
      return ret;
   }
  

   public void kill() throws Exception {
      getSession().killSession();
   }

   public String reInitiateReplication() throws Exception {
      return this.manager.initiateReplication(this.slaveSessionId, this.replPrefix + "_Ver_" + this.ownVersion, this.cascadedReplSlave, this.cascadedReplPrefix, this.initialFilesLocation);
   }
  
   public String getReplPrefix() {
      return this.replPrefix;
   }
  
   public String getReplPrefixGroup() {
      return this.replPrefixGroup;
   }
  
   public String getVersion() {
      return this.ownVersion;
   }

   /**
    * Convenience method enforced by the MBean which returns true if the dispatcher of
    * the slave session is active, false otherwise.
    */
   public boolean isActive() {
      return this.dispatcherActive;
   }
  
   /**
    * Convenience method enforced by the MBean which returns the number of entries in
    * the queue.
    */
   public long getQueueEntries() {
      if (countSingleMessages)
         return this.cbQueueEntries;
      else
         return this.queueEntries;
   }

   /**
    * Convenience method enforced by the MBean which returns true if the real slave is
    * connected or false otherwise.
    */
   public boolean isConnected() {
      return this.connected;
   }

   /**
    * Convenience method enforced by the MBean which returns true if the connection to the
    * real slave is stalled or false otherwise.
    */
   public boolean isStalled() {
      return this.stalled;
   }

   public String getSessionName() {
      return this.sessionName;
   }

   public String getLastMessage() {
      return this.lastMessage;
   }

   public synchronized void checkStatus() {
      if (this.replPrefix == null)
         return;
      log.finest("invoked for '" + this.sessionName + "'");
      I_AdminSession session = null;
      try {
         session = getSession();
      }
      catch (Exception ex) {
         log.severe("an exception occured when retieving the session for '" + this.sessionName + "':" + ex.getMessage());
         ex.printStackTrace();
         return;
      }

      try {
         this.cbQueueEntries = session.getCbQueueNumMsgs();
         // this.messageSeq,
         long[] transactionCountBeforeQueue = this.manager.getCurrentTransactionCount(this.replPrefix);
         // check if the numbers in the queue are correct and fix it
         long pubSubQueueEntries = 0L;
         long maxTransSeq = transactionCountBeforeQueue[0];
         if (transactionSeq == null) // then it is too early
            return;
         for (int i=0; i < this.transactionSeq.length; i++) {
            pubSubQueueEntries += (transactionCountBeforeQueue[i] - this.transactionSeq[i]);
            if (maxTransSeq < transactionCountBeforeQueue[i])
               maxTransSeq = transactionCountBeforeQueue[i];
         }
         this.queueEntries = pubSubQueueEntries + this.ptpQueueEntries;
         this.transactionSeqVisible = maxTransSeq - pubSubQueueEntries;

         if (this.queueEntries != 0 && session != null && session.getCbQueueNumMsgs() == 0) {
            log.warning("Detected wrong number of queue entries: correcting: ptp entries='" + this.ptpQueueEntries + "' total='" + this.queueEntries + "'");
            this.ptpQueueEntries = 0L;
            this.transactionSeq = (long[])transactionCountBeforeQueue.clone();
         }
      }
      catch (Exception ex) {
         log.severe("an exception occured when retieving the number of queue entries for '" + this.sessionName + "':" + ex.getMessage());
         ex.printStackTrace();
         this.queueEntries = -1L;
      }
     
      // isActive
      try {
         this.dispatcherActive = session.getDispatcherActive();
      }
      catch (Exception ex) {
         log.severe("an exception occured when retieving the status of the dispatcher for '" + this.sessionName + "':" + ex.getMessage());
         ex.printStackTrace();
         this.dispatcherActive = false;
      }
     
      try {
         I_AdminSession masterSession = this.manager.getMasterSession(this.replPrefix);
         if (masterSession != null) {
            if (masterSession.isStalled())
               this.masterConn = CONN_STALLED;
            else if (masterSession.getConnectionState().equals(ConnectionStateEnum.ALIVE.toString()))
               this.masterConn = CONN_CONNECTED;
            else
               this.masterConn = CONN_DISCONNECTED;
         }
         else {
            this.masterConn = CONN_DISCONNECTED;
         }
      }
      catch (Exception ex) {
         this.masterConn = CONN_DISCONNECTED;
         ex.printStackTrace();
      }
      // isConnected
      try {
         this.connected = session.getConnectionState().equals(ConnectionStateEnum.ALIVE.toString());
      }
      catch (Exception ex) {
         log.severe("an exception occured when checking if connected for '" + this.sessionName + "':" + ex.getMessage());
         ex.printStackTrace();
         this.connected = false;
      }

      // isStalled
      try {
         this.stalled = session.isStalled();
      }
      catch (Exception ex) {
         log.severe("an exception occured when checking if stalled for '" + this.sessionName + "':" + ex.getMessage());
         ex.printStackTrace();
         this.stalled = false;
      }
     
      // sessionName
      try {
         this.sessionName = session.getLoginName() + "/" + session.getPublicSessionId();
      }
      catch (Exception ex) {
         log.severe("an exception occured when getting the session name:" + ex.getMessage());
         ex.printStackTrace();
      }
   }
  
   public void postCheck(MsgUnit[] processedMsgUnits) throws Exception {
      try {
         initTransactionSequenceIfNeeded("postCheck has been invoked before init");
         if (processedMsgUnits == null) {
            log.severe("The processed Message Units are null");
            return;
         }
         synchronized(this) {
            long msgSeq = 0L;
            long tmpReplKey = -1L;

            if (processedMsgUnits.length > 0) {
               for (int i=0; i < processedMsgUnits.length; i++) {
                  MsgUnit msgUnit = processedMsgUnits[i];

                  long numOfTransactions = msgUnit.getQosData().getClientProperty(ReplicationConstants.NUM_OF_TRANSACTIONS, 1L);
                  if (numOfTransactions > 0L) {
                     long tmpTransactionSeq = msgUnit.getQosData().getClientProperty(ReplicationConstants.TRANSACTION_SEQ, -1L);
                     int prio = ((MsgQosData)msgUnit.getQosData()).getPriority().getInt();
                    
                     boolean absoluteCount = msgUnit.getQosData().getClientProperty(ReplicationConstants.ABSOLUTE_COUNT, false);
                     if (tmpTransactionSeq != -1L && absoluteCount) { // in case the ReplManagerPlugin is not configured as a MimePlugin
                        this.transactionSeq[prio] = tmpTransactionSeq;
                     }
                     else {
                        if (tmpTransactionSeq > this.transactionSeq[5]) // Hack to be removed later (needs always MIME Plugin) TODO
                           this.transactionSeq[prio] += numOfTransactions;
                     }
                     msgSeq = msgUnit.getQosData().getClientProperty(ReplicationConstants.MESSAGE_SEQ, 0L);
                     tmpReplKey = msgUnit.getQosData().getClientProperty(ReplicationConstants.REPL_KEY_ATTR, -1L);
                  }
                  else { // check if an initial data
                     if (numOfTransactions < 0L) {
                        String topicName = msgUnit.getKeyData().getOid();
                        if (this.initialDataTopic != null && this.initialDataTopic.equalsIgnoreCase(topicName)) {
                           this.ptpQueueEntries += numOfTransactions; // negative number so it will decrement
                        }
                     }
                  }
               }     
            }
            setMaxReplKey(tmpReplKey, this.transactionSeq, msgSeq, this.minReplKey, this.ptpQueueEntries);
            if (this.tmpStatus > -1)
               setStatus(this.tmpStatus);
         }
      }
      finally { // lastMessage
         try {
            String tmp = getSession().getLastCallbackException();
            if (tmp.trim().length() > 0 & !lastDispatcherException.equals(tmp)) {
               this.lastDispatcherException = tmp;
               changeLastMessage(tmp);
            }
         }
         catch (Exception ex) {
            log.severe("an exception occured when getting the last dispatcher exception for '" + this.sessionName + "':" + ex.getMessage());
            ex.printStackTrace();
         }
      }
     
   }

   public long getTransactionSeq() {
      if (countSingleMessages)
         return this.maxReplKey;
      else
         return this.transactionSeqVisible;
   }
  
   public static byte[] decompressQueueEntryContent(ReferenceEntry entry) {
      try {
         MsgUnit msgUnit = entry.getMsgUnit();
         if (msgUnit.getContent() == null)
            return new byte[0];
         byte[] content = (byte[])msgUnit.getContent().clone();
         Map cp = new HashMap(msgUnit.getQosData().getClientProperties());
         return ReplManagerPlugin.getContent(MomEventEngine.decompress(new ByteArrayInputStream(content), cp));
      }
      catch (Exception ex) {
         ex.printStackTrace();
         return new byte[0];
      }
   }
  
   public String dumpEntries(int maxNum, long maxSize, String fileName) {
      if (this.queue == null)
         return "The queue is null, the replication must first try to deliver one entry before you can invoke this method";
      if (this.queue.getNumOfEntries() == 0)
         return "The queue for the slave '" + this.name + "' is empty: not dumping anything";
      try {
         List<I_Entry> list = this.queue.peek(maxNum, maxSize);
         FileOutputStream out = new FileOutputStream(fileName);
         for (int i=0; i < list.size(); i++) {
            ReferenceEntry entry = (ReferenceEntry)list.get(i);
            byte[] ret = decompressQueueEntryContent(entry);
            out.write(ret);
         }
         out.close();
         String txt = "successfully dumped " + list.size() + " entries on file '" + fileName + "'";
         log.info(txt);
         return txt;
      }
      catch (IOException ex) {
         String txt = "Could not dump entries because of exception: " + ex.getMessage();
         log.severe(txt);
         ex.printStackTrace();
         return txt;
      }
      catch (Exception ex) {
         String txt = "Could not dump entries because of exception: " + ex.getMessage();
         log.severe(txt);
         ex.printStackTrace();
         return txt;
      }
   }
  
   public String dumpFirstEntry() {
      String prefix = this.initialFilesLocation;
      if (prefix == null)
         prefix = System.getProperty("user.home");
      String name = this.name.replace('/', '-');
      String filename =  prefix + "/" + name + ".qdmp";
      return dumpEntries(1, -1L, filename);
   }
  
   // The following methods are used for JMX to represent the associated / cascaded MBean
  
   /**
    * Returns null if the manager is null or if the cascaded object does not exist.
    */
   private ReplSlave getCascaded() {
      if (this.manager == null)
         return null;
      return (ReplSlave)this.manager.getSlave(this.cascadedReplSlave);
   }
  
   public boolean isCascading() {
      return getCascaded() != null;
   }
  
   /**
    *
    */
   public String getCascadedSessionName() {
      ReplSlave cascaded = getCascaded();
      if (cascaded != null)
         return cascaded.getSessionName();
      return "";
   }
  
   public long getCascadedQueueEntries() {
      ReplSlave cascaded = getCascaded();
      if (cascaded != null)
         return cascaded.getQueueEntries();
      return 0L;
   }
  
   public long getCascadedTransactionSeq() {
      ReplSlave cascaded = getCascaded();
      if (cascaded != null)
         return cascaded.getTransactionSeq();
      return -1L;
   }
  
   public String getCascadedStatus() {
      ReplSlave cascaded = getCascaded();
      if (cascaded != null)
         return cascaded.getStatus();
      return "empty";
   }
  
   public boolean isCascadedActive() {
      ReplSlave cascaded = getCascaded();
      if (cascaded != null)
         return cascaded.isActive();
      return false;
   }
  
   public boolean isCascadedConnected() {
      ReplSlave cascaded = getCascaded();
      if (cascaded != null)
         return cascaded.isConnected();
      return false;
   }
  
   public String getCascadedVersion() {
      ReplSlave cascaded = getCascaded();
      if (cascaded != null)
         return cascaded.getVersion();
      return "";
   }
  
   public String toString() {
      return this.sessionName;
   }
  
   /**
    * Returns a string telling in which state the connection is. It can be stalled, connected or disconnected.
    * @return
    */
   public String getConnection() {
      if (isStalled())
         return CONN_STALLED;
      if (isConnected())
         return CONN_CONNECTED;
      return CONN_DISCONNECTED;
   }
  
   public String getMasterConnection() {
      return this.masterConn;
   }
  
   public String getCascadedConnection() {
      ReplSlave cascadedSlave = getCascaded();
      if (cascadedSlave == null)
         return CONN_DISCONNECTED;
      return cascadedSlave.getConnection();
   }
  
   public String getCascadedMasterConnection() {
      ReplSlave cascadedSlave = getCascaded();
      if (cascadedSlave == null)
         return CONN_DISCONNECTED;
      return cascadedSlave.getMasterConnection();
   }
  
   public void incrementPtPEntries(long numOfTransactions) {
      initTransactionSequenceIfNeeded("incrementPtPEntries has been invoked before init with numOfTransactions='" + numOfTransactions + "'");
      synchronized(this) {
         this.ptpQueueEntries += numOfTransactions;
         // we want to store it
         setMaxReplKey(this.maxReplKey, this.transactionSeq, this.messageSeq, this.minReplKey, this.ptpQueueEntries);
      }
   }

   public void setCountSingleMsg(boolean countSingleMsg) {
      this.countSingleMessages = countSingleMsg;
   }
  
   public boolean isCountSingleMsg() {
      return countSingleMessages;
   }
  
   public int getMaxNumOfEntries() {
      return this.maxNumOfEntries;
   }
  
   /**
    * Sets the maximum number of entries to be sent in one single message.
    * Entries means here the number of entries retrieved from the callback queue.
    */
   public void setMaxNumOfEntries(int maxNumOfEntries) {
      this.maxNumOfEntries = maxNumOfEntries;
      if (persistentInfo != null) {
         if (maxNumOfEntries > 0)
            persistentInfo.put(maxNumOfEntriesKey, "" + maxNumOfEntries);
         else // then remove it from persistency (default will be used)
            persistentInfo.put(maxNumOfEntriesKey, null);
      }
   }
  
   public void onDeadLetter(Map qosClientProperties) {
      String txt = "Holdback Queue Overflow occured";
      if (!lastDispatcherException.equals(txt)) {
         this.lastDispatcherException = txt;
         changeLastMessage(txt);
         setStatus(STATUS_INCONSISTENT);
      }
   }
  
}
TOP

Related Classes of org.xmlBlaster.contrib.replication.ReplSlave

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.