Package org.xmlBlaster.engine.distributor.plugins

Source Code of org.xmlBlaster.engine.distributor.plugins.ConsumableQueuePlugin

/*------------------------------------------------------------------------------
Name:      ConsumableQueuePlugin.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
------------------------------------------------------------------------------*/
package org.xmlBlaster.engine.distributor.plugins;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xmlBlaster.authentication.SessionInfo;
import org.xmlBlaster.engine.MsgUnitWrapper;
import org.xmlBlaster.engine.ServerScope;
import org.xmlBlaster.engine.SubscriptionEvent;
import org.xmlBlaster.engine.SubscriptionInfo;
import org.xmlBlaster.engine.TopicHandler;
import org.xmlBlaster.engine.distributor.I_MsgDistributor;
import org.xmlBlaster.engine.qos.UpdateReturnQosServer;
import org.xmlBlaster.engine.queuemsg.MsgQueueHistoryEntry;
import org.xmlBlaster.engine.queuemsg.MsgQueueUpdateEntry;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.SessionName;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.dispatch.ConnectionStateEnum;
import org.xmlBlaster.util.dispatch.DispatchManager;
import org.xmlBlaster.util.dispatch.DispatchWorker;
import org.xmlBlaster.util.dispatch.I_ConnectionStatusListener;
import org.xmlBlaster.util.plugin.PluginInfo;
import org.xmlBlaster.util.queue.I_Entry;
import org.xmlBlaster.util.queue.I_Queue;
import org.xmlBlaster.util.queuemsg.MsgQueueEntry;

/**
* ConsumableQueuePlugin
*
* @author <a href="mailto:michele@laghi.eu">Michele Laghi</a>
*
*/
public class ConsumableQueuePlugin implements I_MsgDistributor, I_ConnectionStatusListener {

   private final static String ME = "ConsumableQueuePlugin";
   // int status;
   boolean isReady;
   boolean isRunning;
   private Global global;
   private static Logger log = Logger.getLogger(ConsumableQueuePlugin.class.getName());
   private PluginInfo pluginInfo;
   private ServerScope serverScope;
   private String topicId; // <key oid="..."
   private Object mutex = new Object();

   /**
    * The default constructor. Currently does nothing.
    */  
   public ConsumableQueuePlugin() {
      this.isReady = false;
   }

   /**
    * Invoked on status changes when it shall start to distribute
    * entries. This can either happen on publish, on subscribe or when
    * a dispatcher becomes alive again. This method is synchronized to avoid
    * more threads running concurrently (see processHistoryQueue).
    */
   private void toRunning() {
      if (log.isLoggable(Level.FINER)) log.finer("toRunning, isRunning='" + this.isRunning + "' isReady='" + this.isReady + "'");
      synchronized (this.mutex) {
         if (this.isRunning || !this.isReady) return;
         this.isRunning = true;
      }
      try {
         // the global owns a thread pool (Doug Lea's executor pattern)
         this.global.getDispatchWorkerPool().execute(new ConsumableQueueWorker(log, this));
      }
      catch (InterruptedException ex) {
         log.severe("toRunning: exception " + ex.getMessage());
         ex.printStackTrace();
      }
   }

   /**
    * @see org.xmlBlaster.engine.distributor.I_MsgDistributor#distribute(org.xmlBlaster.engine.TopicHandler, org.xmlBlaster.authentication.SessionInfo, org.xmlBlaster.engine.MsgUnitWrapper)
    * Invoked by the TopicHandler on publish or subscribe. Starts the distributor thread and
    * returnes immeditately. From here distribution is handled by another thread.
    **/
   public void distribute(MsgUnitWrapper msgUnitWrapper) {
      if (log.isLoggable(Level.FINER)) log.finer("distribute");
      toRunning();
   }
  
   /**
    * Initializes the plugin
    */
   public void init(Global global, PluginInfo pluginInfo)
      throws XmlBlasterException {
      this.global = global;

      if (log.isLoggable(Level.FINER)) log.finer("init");
      this.pluginInfo = pluginInfo;
      this.serverScope = (org.xmlBlaster.engine.ServerScope)this.global.getObjectEntry(Constants.OBJECT_ENTRY_ServerScope);
      TopicHandler topicHandler = (TopicHandler)this.pluginInfo.getUserData();
      this.topicId = topicHandler.getUniqueKey();
      this.isReady = true;
      toRunning();
   }

   public String getType() {
      return this.pluginInfo.getType();
   }

   public String getVersion() {
      return this.pluginInfo.getVersion();
   }

   /**
    * It removes all subscriptions done on this topic
    */
   public void shutdown() throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) log.finer("shutdown");
      SubscriptionInfo[] subs = this.serverScope.getTopicAccessor().getSubscriptionInfoArrDirtyRead(this.topicId);
      for (int i=0; i < subs.length; i++) subscriptionRemove(new SubscriptionEvent(subs[i]));
      this.isReady = false;
   }
  
   private final DispatchManager getDispatchManager(SubscriptionInfo subscriptionInfo) {
      if (subscriptionInfo == null) {
         log.severe("getDispatchManager the subscriptionInfo object is null");
         Thread.dumpStack();
         return null;
      }
      SessionInfo sessionInfo = subscriptionInfo.getSessionInfo();
      if (sessionInfo == null) {
         log.severe("getDispatchManager the sessionInfo object is null");
         Thread.dumpStack();
         return null;
      }
      DispatchManager dispatchManager = sessionInfo.getDispatchManager();
      if (dispatchManager == null) {
         log.severe("getDispatchManager the dispatcherManager object is null");
         Thread.dumpStack();
         return null;
      }
      return dispatchManager;
   }
  
   /**
    * Invoked when a subscriber is added to the TopicHandler
    * @param subscriptionInfo
    */
   public void subscriptionAdd(SubscriptionEvent e)
      throws XmlBlasterException {
      SubscriptionInfo subscriptionInfo = e.getSubscriptionInfo();
      if (log.isLoggable(Level.FINER)) log.finer("onAddSubscriber");
      DispatchManager dispatchManager = getDispatchManager(subscriptionInfo);
      if (dispatchManager != null) dispatchManager.addConnectionStatusListener(this);
      this.isReady = true;
      toRunning();
   }
  
   /**
    * Invoked when a subscriber is removed from the TopicHandler
    * @param subscriptionInfo
    */
   public void subscriptionRemove(SubscriptionEvent e) throws XmlBlasterException {
      SubscriptionInfo subscriptionInfo = e.getSubscriptionInfo();
      if (log.isLoggable(Level.FINER)) log.finer("onRemoveSubscriber");
      DispatchManager dispatchManager = getDispatchManager(subscriptionInfo);
      if (dispatchManager != null) dispatchManager.removeConnectionStatusListener(this);
   }

   /**
    * Event arriving from one DispatchManager telling this plugin it can
    * start distribute again.
    */
   public void toAlive(DispatchManager dispatchManager, ConnectionStateEnum oldState) {
      if (log.isLoggable(Level.FINER)) log.finer("toAlive");
      this.isReady = true;
      toRunning();
   }

   public void toPolling(DispatchManager dispatchManager, ConnectionStateEnum oldState) {
   }

   public void toDead(DispatchManager dispatchManager, ConnectionStateEnum oldState, XmlBlasterException xmlBlasterException) {
   }

   public void toAliveSync(DispatchManager dispatchManager, ConnectionStateEnum oldState) {
   }
  
   /**
    * Takes entries from the history queue and distributes it to the dispatcher
    * framework until there are entries available or until the dispatcher framework
    * is alive.
    */
   void processHistoryQueue() {
      if (log.isLoggable(Level.FINER)) log.finer("processQueue");
      try {
         List<I_Entry> lst = null;
         while (true) {
            //synchronized(this) {
               TopicHandler topicHandler = this.serverScope.getTopicAccessor().access(this.topicId);
               try {
                  I_Queue historyQueue = topicHandler.getHistoryQueue();
                  if (historyQueue == null) {
                     this.isRunning = false;
                     break;
                  }
                  lst = historyQueue.peek(-1, -1L);
                  if (log.isLoggable(Level.FINE)) log.fine("processQueue: processing '" + lst.size() + "' entries from queue");
                  if (lst == null || lst.size() < 1) {
                     this.isRunning = false;
                     break;
                  }
               }
               finally {
                  this.serverScope.getTopicAccessor().release(topicHandler);
               }
            //}
           

            SubscriptionInfo[] subInfoArr = this.serverScope.getTopicAccessor().getSubscriptionInfoArrDirtyRead(this.topicId);
            ArrayList subInfoList = new ArrayList();
            for (int i=0; i < subInfoArr.length; i++) subInfoList.add(subInfoArr[i]);
           
            for (int i=0; i < lst.size(); i++) {
               if (!this.isReady) return;
               MsgQueueHistoryEntry entry = (MsgQueueHistoryEntry)lst.get(i);
               MsgUnitWrapper msgUnitWrapper = (entry).getMsgUnitWrapper();
               if (msgUnitWrapper != null) {
                  if (!this.distributeOneEntry(msgUnitWrapper, entry, subInfoList)) {
                     this.isReady = false;
                     this.isRunning = false;
                     return;
                  }
               }
            }
             
         }
         this.isReady = true;
      }
      catch (Throwable ex) {
         this.isReady = false;
         this.isRunning = false;
         ex.printStackTrace();
         log.severe("processQueue: " + ex.getMessage());
      }
   }

   /**
    * Distributes one single entry taken from the history queue. This method
    * is strict, it does not throw any exceptions. If one exception occurs
    * inside this method, the distribution is interrupted, a dead letter is
    * generated and the entry is removed from the history queue.
    *
    * @param subInfoList contains the SubscriptionInfo objects to scan. Once the
    *        message is processed by one of the dispatchers, the associated
    *        SessionInfo is put at the end of the list to allow some simple
    *        load balancing mechanism.
    *
    * @return true if the entry has been removed from the history queue. This happens
    * if the entry could be sent successfully, or if distribution was given up due to
    * an exception. It returns false if none of the subscribers were able to receive
    * the message (to tell the invoker not to continue with distribution until
    * the next event.
    */
   private boolean distributeOneEntry(MsgUnitWrapper msgUnitWrapper, MsgQueueHistoryEntry entry, List subInfoList) {
      try {

         if (msgUnitWrapper == null) {
            log.severe("distributeOneEntry() MsgUnitWrapper is null");
            Thread.dumpStack();
            givingUpDistribution(null, msgUnitWrapper, entry, null);
            return true; // let the loop continue: other entries could be OK
         }

         if (log.isLoggable(Level.FINER)) log.finer("distributeOneEntry '" + msgUnitWrapper.getUniqueId() + "' '" + msgUnitWrapper.getKeyOid() + "'");
         // Take a copy of the map entries (a current snapshot)
         // If we would iterate over the map directly we can risk a java.util.ConcurrentModificationException
         // when one of the callback fails and the entry is removed by the callback worker thread

         SubscriptionInfo[] subInfoArr = (SubscriptionInfo[])subInfoList.toArray(new SubscriptionInfo[subInfoList.size()]);
         for (int ii=0; ii<subInfoArr.length; ii++) {
            SubscriptionInfo sub = subInfoArr[ii];
            if (TopicHandler.isDirtyRead(sub, msgUnitWrapper)) {
               log.severe("ConsumableQueuePlugin used together with 'dirtyRead' is not supported");
               TopicHandler topicHandler = this.serverScope.getTopicAccessor().access(this.topicId);
               if (topicHandler == null) return true;
               try {
                  I_Queue srcQueue = topicHandler.getHistoryQueue();
                  if (srcQueue != null) srcQueue.removeRandom(entry);
               }
               finally {
                  this.serverScope.getTopicAccessor().release(topicHandler);
               }
               return true; // even if it has not been sent
            }
         }  

         for (int ii=0; ii<subInfoArr.length; ii++) {
            SubscriptionInfo sub = subInfoArr[ii];
           
            if (!TopicHandler.subscriberMayReceiveIt(sub, msgUnitWrapper)) continue;
            //Has no effect:
            //if (!this.topicHandler.checkIfAllowedToSend(null, sub)) continue;

            // this is specific for this plugin
            if (sub.getSessionInfo().getDispatchManager() == null) continue;
            if (!sub.getSessionInfo().getDispatchManager().getDispatchConnectionsHandler().isAlive()) continue;

            try {

               try {
                  TopicHandler topicHandler = this.serverScope.getTopicAccessor().access(this.topicId);
                  if (topicHandler == null) return true;
                  try {
                     // the 'false' here is to tell the filter not to send a dead letter in case of an ex
                     if (!topicHandler.checkFilter(null, sub, msgUnitWrapper, false)) continue;
                  }
                  finally {
                     this.serverScope.getTopicAccessor().release(topicHandler);
                  }
               }
               catch (XmlBlasterException ex) {
                  // continue;
                  givingUpDistribution(sub, msgUnitWrapper, entry, ex);
                  return true; // because the entry has been removed from the history queue
               }

               // put the current dispatcher at the end of the list for next invocation (round robin)
               subInfoList.remove(sub);
               subInfoList.add(sub);
              
               MsgQueueUpdateEntry updateEntry = TopicHandler.createEntryFromWrapper(msgUnitWrapper,sub);

               UpdateReturnQosServer retQos = doDistribute(sub, updateEntry);

               if (log.isLoggable(Level.FINE)) {
                  if (retQos == null) log.fine("distributeOneEntry: the return object was null: callback has not sent the message (dirty reads ?)");
               }
               if (retQos == null || retQos.getException() == null) {
                  TopicHandler topicHandler = this.serverScope.getTopicAccessor().access(this.topicId);
                  if (topicHandler == null) return true;
                  try {
                     I_Queue srcQueue = topicHandler.getHistoryQueue();
                     if (srcQueue != null) srcQueue.removeRandom(entry); // success
                  }
                  finally {
                     this.serverScope.getTopicAccessor().release(topicHandler);
                  }
                  if (log.isLoggable(Level.FINE)) log.fine("distributeOneEntry: successfully removed entry from queue");
                  return true;
               }
               else {
                  log.severe("distributeOneEntry an exception occured: " + retQos.getException().getMessage());
                  Throwable ex = retQos.getException();
                  // continue if it is a communication exception stop otherwise
                  if (ex instanceof XmlBlasterException && ((XmlBlasterException)ex).isCommunication()) continue;
                  // we pass null for the exception since we don't want to shut down the dispatcher
                  givingUpDistribution(sub, msgUnitWrapper, entry, null);
                  return true; //since removed
               }
            }
            catch (Throwable e) {
               e.printStackTrace();
               givingUpDistribution(sub, msgUnitWrapper, entry, e);
               return true;
            }
         }
      }
      catch (Throwable ex) {
         ex.printStackTrace();
         log.severe("distributeOneEntry " + ex.getMessage());
         givingUpDistribution(null, msgUnitWrapper, entry, ex);        
         // TODO or should we return true here to allow to continue ?
         // I think it is a serious ex: probably does not make sense to cont.
      }
      return false;
   }

   private void givingUpDistribution(SubscriptionInfo sub, MsgUnitWrapper msgUnitWrapper, MsgQueueEntry entry, Throwable e) {
      try {
         String id = "";
         if (sub != null) id = sub.getSessionInfo().getId();
         String exTxt = "";
         if (e != null) exTxt = e.toString();
         SessionName publisherName = msgUnitWrapper.getMsgQosData().getSender();
         if (log.isLoggable(Level.FINE)) log.fine("Sending of message from " + publisherName + " to " +
                            id + " failed: " + exTxt);
              
         if (sub != null && e != null)
            sub.getSessionInfo().getDispatchManager().internalError(e); // calls MsgErrorHandler
         else {
            this.serverScope.getRequestBroker().deadMessage(new MsgQueueEntry[] { entry }, null, ME + ".givingUpDistribution: " + exTxt);
         }
         // remove the entry from the history queue now that a dead letter has been sent.
         TopicHandler topicHandler = this.serverScope.getTopicAccessor().access(this.topicId);
         try {
            I_Queue historyQueue = topicHandler.getHistoryQueue();
            if (historyQueue != null)
               historyQueue.removeRandom(entry);
         }
         finally {
            this.serverScope.getTopicAccessor().release(topicHandler);
         }
      }
      catch (XmlBlasterException ex) {
         log.severe("givingUpDistribution: " + ex.getMessage());
         ex.printStackTrace();
      }
   }

   /**
    * Enforced by the I_DistributionInterceptor interface. It sends sychronously to
    * the DispatchWorker this entry.
    */   
   private UpdateReturnQosServer doDistribute(SubscriptionInfo sub, MsgQueueUpdateEntry entry) throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) log.finer("doDistribute");
      // this is a sync call (all in the same thread)
      entry.setWantReturnObject(true);
      DispatchWorker worker = new DispatchWorker(this.global, sub.getSessionInfo().getDispatchManager());
      ArrayList list = new ArrayList();
      list.add(entry);
      worker.run(list);     
      return (UpdateReturnQosServer)entry.getReturnObj();
   }

   /**
    * @see org.xmlBlaster.engine.I_SubscriptionListener#getPriority()
    */
   public Integer getPriority() {
      return PRIO_05;
   }
}
   
TOP

Related Classes of org.xmlBlaster.engine.distributor.plugins.ConsumableQueuePlugin

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.