Package org.xmlBlaster.client

Source Code of org.xmlBlaster.client.StreamingCallback

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

package org.xmlBlaster.client;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.xmlBlaster.client.key.UpdateKey;
import org.xmlBlaster.client.qos.ConnectQos;
import org.xmlBlaster.client.qos.UpdateQos;
import org.xmlBlaster.client.queuemsg.MsgQueuePublishEntry;
import org.xmlBlaster.jms.XBConnectionMetaData;
import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.I_Timeout;
import org.xmlBlaster.util.MsgUnit;
import org.xmlBlaster.util.Timeout;
import org.xmlBlaster.util.Timestamp;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.def.Constants;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.dispatch.ConnectionStateEnum;
import org.xmlBlaster.util.key.MsgKeyData;
import org.xmlBlaster.util.qos.ClientProperty;
import org.xmlBlaster.util.qos.MsgQosData;
import org.xmlBlaster.util.qos.storage.ClientQueueProperty;
import org.xmlBlaster.util.queue.I_Entry;
import org.xmlBlaster.util.queue.I_Queue;
import org.xmlBlaster.util.queue.StorageId;

import EDU.oswego.cs.dl.util.concurrent.LinkedQueue;
import EDU.oswego.cs.dl.util.concurrent.Mutex;

/**
* StreamingCallback
* @author <a href="mailto:michele@laghi.eu">Michele Laghi</a>
*/
public class StreamingCallback implements I_Callback, I_Timeout, I_ConnectionStateListener {

   /**
    *
    * Writer needed since the out stream must be written from a thread which does not
    * die before the thread which reads the in counterpart. For some "strange" reason
    * the implementation of the Pipe streams makes a check if the thread which has
    * made the last write operation on the out stream still is valid. If not, a Dead
    * End IO Exception is thrown when reading.
    *
    * @author <a href="mailto:michele@laghi.eu">Michele Laghi</a>
    */
   class Writer extends Thread {

      class WriterData extends Mutex {
         private OutputStream outStrm;
         private byte[] data;
         private Throwable exception;
        
         public WriterData(OutputStream out, byte[] data) {
            super();
            this.outStrm = out;
            this.data = data;
         }
      }

      private LinkedQueue channel;
     
      public Writer(String name) {
         super(name);
         this.channel = new LinkedQueue();
         setDaemon(true);
         start();
      }

      public Writer() {
         super();
         this.channel = new LinkedQueue();
         setDaemon(true);
         start();
      }
     
      public synchronized void write(OutputStream outStream, byte[] buf) throws InterruptedException, XmlBlasterException {
         WriterData data = new WriterData(outStream, buf);
         try {
            data.acquire();
            this.channel.put(data);
            data.acquire(); // waits until the other thread is finished
            if (data.exception != null)
               throw new XmlBlasterException(global, ErrorCode.USER_UPDATE_HOLDBACK, "write: a throwable occured", "", data.exception);
         }
         finally {
            data.release();
         }
      }

      public synchronized void close(OutputStream outStream) throws InterruptedException, XmlBlasterException {
         WriterData data = new WriterData(outStream, null);
         try {
            data.acquire();
            this.channel.put(data);
            data.acquire(); // waits until the other thread is finished
            if (data.exception != null)
               throw new XmlBlasterException(global, ErrorCode.USER_UPDATE_HOLDBACK, "close: a throwable occured", "", data.exception);
         }
         finally {
            data.release();
         }
      }

      /**
       * @see java.lang.Thread#run()
       */
      public void run() {
         while (true) {
            try {
               WriterData writerData = (WriterData)this.channel.take();
               try {
                  if (writerData.outStrm != null) {
                     if (writerData.data != null) {

                        int bytesLeft = writerData.data.length;
                        int bytesRead = 0;
                        final int MAX_CHUNK_SIZE = 4096;
                        while (bytesLeft > 0) {
                           int toRead = bytesLeft > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : bytesLeft;
                           writerData.outStrm.write(writerData.data, bytesRead, toRead);
                           writerData.outStrm.flush();
                           bytesRead += toRead;
                           bytesLeft -= toRead;
                        }
                        // writerData.out.write(0);
                        // writerData.out.flush();
                       
                        // these would block (probably the pipes are not the best in the world
                        // writerData.out.write(writerData.data);
                        // writerData.out.flush();
                     }
                     else
                        writerData.outStrm.close();
                  }
               }
               catch (Throwable e) {
                  writerData.exception = e;
               }
               finally {
                  writerData.release();
               }
            }
            catch (Throwable e) {
               if (e.getMessage().indexOf("Pipe closed") < 0) {
                  log.warning("An exception occured when writing to the stream: ' " + e.getMessage());
                  e.printStackTrace();
               }
               else if (log.isLoggable(Level.FINE)) {
                  log.fine("The pipe was closed, which resulted in an IO Exception. It can happen when the client has returned before reading the complete message");
                  e.printStackTrace();
               }
            }
         }
      }
   }
  
  
   class ExecutionThread extends Thread {
     
      private String cbSessionId_;
      private UpdateKey updateKey_;
      private byte[] content_;
      private UpdateQos updateQos_;
     
      public ExecutionThread(String cbSessId, UpdateKey updKey, byte[] content, UpdateQos updQos) {
         this.cbSessionId_ = cbSessId;
         this.updateKey_ = updKey;
         this.content_ = content;
         this.updateQos_ = updQos;
        
      }
     
      public void run() {
         try {
            ret = updateNewMessage(cbSessionId_, updateKey_, content_, updateQos_);
            clearQueue();
         }
         catch (Throwable e) {
            setException(e);
            e.printStackTrace();
         }
         finally {
            try {
               if (in != null)
                  in.close();
            }
            catch (IOException e) {
               e.printStackTrace();
            }
            mutex.release();
         }
      }
   };

   private static Logger log = Logger.getLogger(StreamingCallback.class.getName());
   public final static String ENTRY_CB_SESSION_ID = "__entryCbSessionId";
  
   private I_StreamingCallback callback;

   private Global global;
   private PipedOutputStream out;
   private PipedInputStream in;
   private XmlBlasterException ex;
   private String ret;
   private String cbSessionId;
   private Writer writer;
   /** The time to wait in ms until returning when waiting (if zero or negative inifinite) */
   private long waitForChunksTimeout;
   // private long waitForClientReturnTimeout;
   private Timeout timer;
   private Timestamp timestamp; // the key for the timeout timer (can be null)
   private I_Queue queue; // optional client side queue
   private boolean useQueue;
   private boolean initialized;
   private boolean lastMessageCompleted = true;
   private final Mutex mutex;
  
   private void reset() throws XmlBlasterException {
      this.out = null;
      this.in = null;
      this.ret = null;
      this.cbSessionId = null;
   }
  
   public StreamingCallback(Global global, I_StreamingCallback callback) throws XmlBlasterException {
      this(global, callback, 0L, 0L, false);
   }
  
   /**
    *
    * @param callback
    */
   public StreamingCallback(Global global, I_StreamingCallback callback, long waitForChunksTimeout, long waitForClientReturnTimeout, boolean useQueue)
      throws XmlBlasterException {
      this.callback = callback;
      this.global = global;
      this.mutex = new Mutex();
      String writerName = StreamingCallback.class.getName() + "-writer";
      synchronized(this.global) {
         this.writer = (Writer)this.global.getObjectEntry(writerName);
         if (this.writer == null) {
            this.writer = new Writer();
            this.global.addObjectEntry(writerName, this.writer);
         }
      }
      this.waitForChunksTimeout = waitForChunksTimeout;
      // this.waitForClientReturnTimeout = waitForClientReturnTimeout;
      if (this.waitForChunksTimeout > 0L) {
         String timerName = StreamingCallback.class.getName() + "-timer";
         synchronized(this.global) {
            this.timer = (Timeout)this.global.getObjectEntry(timerName);
            if (this.timer == null) {
               this.timer = new Timeout(timerName);
               this.global.addObjectEntry(timerName, this.timer);
            }
         }
      }
      this.useQueue = useQueue;
      // TODO latch until connected to avoit early updates
   }
  
   /**
    *
    * @return the number of delivered entries from local client update queue.
    */
   public final int sendInitialQueueEntries() throws XmlBlasterException {
      if (this.queue == null)
         return 0;
      List<I_Entry> list = this.queue.peek(-1, -1L);
      for (int i=0; i < list.size(); i++) {
         MsgQueuePublishEntry entry = (MsgQueuePublishEntry)list.get(i);
         MsgKeyData key = entry.getMsgKeyData();
         MsgQosData qos =(MsgQosData)entry.getMsgUnit().getQosData();
         byte[] cont = entry.getMsgUnit().getContent();
         String entryCbSessionId = qos.getClientProperty(ENTRY_CB_SESSION_ID, (String)null);
         qos.getClientProperties().remove(ENTRY_CB_SESSION_ID);
         final boolean isExternal = false; // we don't want to store these entries since already here
         updateInternal(entryCbSessionId, new UpdateKey(key), cont, new UpdateQos(this.global, qos), isExternal);
      }
      this.queue.clear();
      return list.size();
   }
  
   private final void storeEntry(String cbSessId, UpdateKey key, byte[] cont, UpdateQos qos) throws XmlBlasterException {
      if (this.queue == null)
         return;
      final boolean ignorePutInterceptor = false;
      if (cbSessId != null) {
         String oldCbSessionId = qos.getClientProperty(ENTRY_CB_SESSION_ID, (String)null);
         if (oldCbSessionId != null && !oldCbSessionId.equals(cbSessId)) {
            log.warning("the client property '" + ENTRY_CB_SESSION_ID + "' is a reserved word, we will overwrite its value='" + oldCbSessionId + "' to be '" + cbSessionId + "'");
            ClientProperty prop = new ClientProperty(ENTRY_CB_SESSION_ID, null, null, cbSessId);
            qos.getClientProperties().put(prop.getName(), prop);
         }
      }
      MsgUnit msgUnit = new MsgUnit(key.getData(), cont, qos.getData());
      MsgQueuePublishEntry entry = new MsgQueuePublishEntry(this.global, msgUnit, this.queue.getStorageId());
      this.queue.put(entry, ignorePutInterceptor);
   }
  
   /**
    * @see org.xmlBlaster.client.I_Callback#update(java.lang.String, org.xmlBlaster.client.key.UpdateKey, byte[], org.xmlBlaster.client.qos.UpdateQos)
    */
   public String updateStraight(String cbSessId, UpdateKey updKey, byte[] cont, UpdateQos updQos) throws XmlBlasterException, IOException {
      log.fine("cbSessionId='" + cbSessId + "'");
      ByteArrayInputStream bais = new ByteArrayInputStream(cont);
      return this.callback.update(cbSessId, updKey, bais, updQos);
   }
  
   /**
    * @see org.xmlBlaster.client.I_Callback#update(java.lang.String, org.xmlBlaster.client.key.UpdateKey, byte[], org.xmlBlaster.client.qos.UpdateQos)
    */
   public String updateNewMessage(String cbSessId, UpdateKey updKey, byte[] cont, UpdateQos updQos) throws XmlBlasterException, IOException {
      log.fine("cbSessionId='" + cbSessId + "'");
      return this.callback.update(cbSessId, updKey, in, updQos);
   }

   private final boolean isFirstChunk(UpdateQos qos) {
      int seq = qos.getClientProperty(Constants.addJmsPrefix(XBConnectionMetaData.JMSX_GROUP_SEQ, log), 0);
      return seq == 0;
   }
  
   private final boolean isLastChunk(UpdateQos qos) {
      boolean hasGroupId = qos.getClientProperty(Constants.addJmsPrefix(XBConnectionMetaData.JMSX_GROUP_ID, log), (String)null) != null;
      if (!hasGroupId)
         return true;
      return qos.getClientProperty(Constants.addJmsPrefix(XBConnectionMetaData.JMSX_GROUP_EOF, log), false);
   }
  
   private final ClientProperty getProp(String key, UpdateQos qos) {
      return qos.getClientProperty(Constants.addJmsPrefix(key, log));
   }
  
   /**
    * @see org.xmlBlaster.client.I_Callback#update(java.lang.String, org.xmlBlaster.client.key.UpdateKey, byte[], org.xmlBlaster.client.qos.UpdateQos)
    */
   public String update(String cbSessId, UpdateKey updKey, byte[] cont, UpdateQos updQos) throws XmlBlasterException {
      boolean sendInitial = this.queue != null && this.lastMessageCompleted && this.queue.getNumOfEntries() > 0;
      if (sendInitial)
         sendInitialQueueEntries();
     
      final boolean isExternal = true;
      log.fine("cbSessionId='" + cbSessId + "'");
      return updateInternal(cbSessId, updKey, cont, updQos, isExternal);
   }
  
   /**
    * @see org.xmlBlaster.client.I_Callback#update(java.lang.String, org.xmlBlaster.client.key.UpdateKey, byte[], org.xmlBlaster.client.qos.UpdateQos)
    */
   private final String updateInternal(String cbSessId, UpdateKey updKey, byte[] cont, UpdateQos updQos, boolean isExternal) throws XmlBlasterException {
      this.lastMessageCompleted = false;
      boolean doStore = isExternal;
      boolean isLastChunk = false;
      try {
         log.fine("entering with cbSessionId='" + cbSessId + "'");
         if (this.timer != null && this.timestamp != null) { // no need to be threadsafe since update is single thread
            this.timer.removeTimeoutListener(this.timestamp);
            this.timestamp = null;
         }
         ClientProperty exProp = getProp(XBConnectionMetaData.JMSX_GROUP_EX, updQos);
         // TODO Check if this exception really should be thrown: I think it shall not be thrown since it is an exception
         // which occured when publishing and this is the information that the update should return
         if (exProp != null)
            throw new XmlBlasterException(this.global, ErrorCode.USER_UPDATE_INTERNALERROR, "update", "An exception occured on a chunk when updating. " + updQos.toXml());
         isLastChunk = isLastChunk(updQos);
        
         synchronized(this) {
            consumeExceptionIfNotNull();
            if (this.ret != null) {
               clearQueue();
               return ret;
            }
         }
        
         if (isLastChunk) { // no need to store the last message since sync return
            if (isFirstChunk(updQos)) {
               // TODO a sync to wait until cleared (the updateStraight after the sync, not inside).
               try {
                  return updateStraight(cbSessId, updKey, cont, updQos);
               }
               catch (IOException e) {
                  throw new XmlBlasterException(this.global, ErrorCode.INTERNAL, "StreamingCallback", "update: exception occured.", e);
               }
              
            }
           
            try {
               if (cont != null && cont.length > 0) {
                  this.writer.write(this.out, cont);
               }
              
               this.writer.close(this.out);
               // wait until the client has returned his method.
               try {
                  mutex.acquire();
                  consumeExceptionIfNotNull();
                  clearQueue();
                  return this.ret;
               }
               finally {
                  mutex.release();
               }
            }
            catch (InterruptedException e) {
               throw new XmlBlasterException(this.global, ErrorCode.INTERNAL, "StreamingCallback", "update", e);
            }
            finally {
               reset();
            }
         }
         else { // it is not the last message
            if (this.timer != null)
               this.timestamp = this.timer.addTimeoutListener(this, this.waitForChunksTimeout, null);
            try {
               if (isFirstChunk(updQos)) {
                  this.mutex.acquire();
                  this.cbSessionId = cbSessId;
                  this.out = new PipedOutputStream();
                  this.in = new PipedInputStream(this.out);
                  ExecutionThread thread = new ExecutionThread(cbSessId, updKey, cont, updQos);
                  thread.start();
               }
               else { // check if the message is complete
                  /*
                  if (this.oldGroupId == null) {
                     try {
                        mutex.acquire();
                        throw new XmlBlasterException(this.global, ErrorCode.INTERNAL, "StreamingCallback", "update: The message is not the first of a group but the previous one was already completed.");
                     }
                     finally {
                        mutex.release();
                     }
                  }
                  */
               }
               this.writer.write(this.out, cont);
            }
            catch (InterruptedException e) {
               throw new XmlBlasterException(this.global, ErrorCode.INTERNAL, "StreamingCallback", "update", e);
            }
            catch (IOException e) {
               throw new XmlBlasterException(this.global, ErrorCode.INTERNAL, "StreamingCallback", "update", e);
            }
            if (doStore)
               storeEntry(cbSessId, updKey, cont, updQos);
            // and return a fake positive response.
            return Constants.RET_OK;
         }
        
      }
      catch (XmlBlasterException e) {
         try {
            this.writer.close(this.out);
         }
         catch (InterruptedException e1) {
            e1.printStackTrace();
         }
         this.lastMessageCompleted = true;
         throw e;
      }
      catch (Throwable e) {
         e.printStackTrace();
         throw new XmlBlasterException(this.global, ErrorCode.USER_UPDATE_HOLDBACK, "throwable in updateInternal", "", e);
      }
      finally {
         if (isLastChunk) {
            this.lastMessageCompleted = true;
         }
         log.fine("Leaving method");
      }
   }

   /**
    * It is used here to inform the user update method that a timeout occured, it will throw
    * an IOException when reading the in stream of the update method.
    * @see org.xmlBlaster.util.I_Timeout#timeout(java.lang.Object)
    */
   public void timeout(Object userData) {
      try {
         this.writer.close(this.out);
      }
      catch (Throwable e) {
         // we can not make it threadsafe so we must protect against possible NPE Exceptions
         e.printStackTrace();
      }
     
   }

   private final void clearQueue() {
      if (queue != null) {
         log.fine("Clear the queue " + this.queue.getStorageId());
         queue.clear();
      }
   }

   /**
    * Always makes a USER_UPDATE_HOLDBACK Exception out of it, no matter what the original exception
    * was.
    * @param ex
    */
   private synchronized void setException(Throwable ex) {
      if (ex instanceof XmlBlasterException) {
         XmlBlasterException tmp = (XmlBlasterException)ex;
         if (tmp.getErrorCode().equals(ErrorCode.USER_UPDATE_HOLDBACK))
            this.ex = tmp;
         else
            this.ex = new XmlBlasterException(global, ErrorCode.USER_UPDATE_HOLDBACK, "StreamingCallback", "update: exception occured.", ex);
      }
      else {
         this.ex = new XmlBlasterException(global, ErrorCode.USER_UPDATE_HOLDBACK, "StreamingCallback", "update: exception occured.", ex);
      }
   }
  
   /**
    * returns the exception (if any) and resets it.
    * @return
    */
   private synchronized void consumeExceptionIfNotNull() throws XmlBlasterException {
      XmlBlasterException e = this.ex;
      if (e != null) {
         this.ex = null;
         throw e;
      }
   }
  
   // implementation of interface I_ConnectionStateListener
  
   /**
    * @see org.xmlBlaster.client.I_ConnectionStateListener#reachedAlive(org.xmlBlaster.util.dispatch.ConnectionStateEnum, org.xmlBlaster.client.I_XmlBlasterAccess)
    */
   public synchronized void reachedAlive(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
      log.fine("I am alive now");
      // only used on first connect after it is ignored.
      if (this.initialized)
         return;
    
      if (this.useQueue) {
         log.info("going to instance the queue");
         ConnectQos connectQos = connection.getConnectQos();
         ClientQueueProperty prop = connectQos.getClientQueueProperty();
         StorageId storageId = ((XmlBlasterAccess) connection).createStorageId(Constants.RELATING_CLIENT_UPDATE);
         try {
            this.queue = this.global.getQueuePluginManager().getPlugin(prop.getType(), prop.getVersion(), storageId,
                  connectQos.getClientQueueProperty());
            if (((XmlBlasterAccess)connection).isCallbackDispatcherActive())
               sendInitialQueueEntries();
         }
         catch (XmlBlasterException e) {
            log.severe("An exception occured when trying to initialize the callback client queue: " + e.getMessage());
            e.printStackTrace();
         }
      }
     
      this.initialized = true;
   }

   /**
    * @see org.xmlBlaster.client.I_ConnectionStateListener#reachedAlive(org.xmlBlaster.util.dispatch.ConnectionStateEnum, org.xmlBlaster.client.I_XmlBlasterAccess)
    */
   public synchronized void reachedAliveSync(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
      log.fine("I am alive and sync now");
   }

   /* (non-Javadoc)
    * @see org.xmlBlaster.client.I_ConnectionStateListener#reachedDead(org.xmlBlaster.util.dispatch.ConnectionStateEnum, org.xmlBlaster.client.I_XmlBlasterAccess)
    */
   public void reachedDead(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
      // TODO Auto-generated method stub
     
   }

   /* (non-Javadoc)
    * @see org.xmlBlaster.client.I_ConnectionStateListener#reachedPolling(org.xmlBlaster.util.dispatch.ConnectionStateEnum, org.xmlBlaster.client.I_XmlBlasterAccess)
    */
   public void reachedPolling(ConnectionStateEnum oldState, I_XmlBlasterAccess connection) {
      // TODO Auto-generated method stub
     
   }
  
}
TOP

Related Classes of org.xmlBlaster.client.StreamingCallback

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.