Package org.xmlBlaster.protocol.http.appletproxy

Source Code of org.xmlBlaster.protocol.http.appletproxy.PushHandler

/*------------------------------------------------------------------------------
Name:      PushHandler.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
Comment:   Handling callback over http
------------------------------------------------------------------------------*/
package org.xmlBlaster.protocol.http.appletproxy;

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

import org.xmlBlaster.util.Global;
import org.xmlBlaster.util.def.ErrorCode;
import org.xmlBlaster.util.Base64;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.I_Timeout;
import org.xmlBlaster.util.Timeout;
import org.xmlBlaster.util.Timestamp;
import org.xmlBlaster.client.I_XmlBlasterAccess;
import org.xmlBlaster.client.I_Callback;
import org.xmlBlaster.client.key.UpdateKey;
import org.xmlBlaster.client.protocol.http.common.I_XmlBlasterAccessRaw;
import org.xmlBlaster.client.protocol.http.common.ObjectOutputStreamMicro;
import org.xmlBlaster.client.qos.UpdateQos;

import javax.servlet.*;
import javax.servlet.http.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Hashtable;

/**
* This handles and hides the different http push modes when sending
* data back to the applet through method update().
* <br />
* Push mode works with keeping a permanent http connection
* <p />
* TODO:
*   HTTP 1.1 specifies rfc2616 that the connection stays open as the
*   default case. How must this code be changed?
* <p />
* See Java Servlet Programming from Jason Hunter
* @author Marcel Ruff xmlBlaster@marcelruff.info
*/
public class PushHandler implements I_Callback, I_Timeout
{
   private String ME  = "PushHandler";
   private Global glob;
   private static Logger log = Logger.getLogger(PushHandler.class.getName());

   /**
    * Ping the applet every 10 seconds.
    */
   private long pingInterval = 10000L;
   private final Timeout timeout;
   private Timestamp pingTimeoutHandle;
   private long pingCounter;
   private long missingPongs;

   private I_XmlBlasterAccess xmlBlasterAccess;
   private I_Callback callbackInterceptor;

   private HttpServletResponse res;
   private String sessionId;

   /** Current http connection state */
   private boolean closed = false;
   private ServletOutputStream outMulti;

   /* Send Base64 all in one line */
   boolean isChunked = false;

   /**
    * Use the persistent HTTP callback connection.
    * @param res The response object
    * @param sessionId The applet id
    * @param loginName For loggin only
    * @param xmlBlasterAccess Not yet logged in
    */
   public PushHandler(HttpServletRequest req, HttpServletResponse res, String sessionId,
                          String loginName, I_XmlBlasterAccess xmlBlasterAccess, Timeout timeout)
                               throws ServletException, IOException {


      this.res = res;
      this.sessionId = sessionId;
      this.xmlBlasterAccess = xmlBlasterAccess;
      this.glob = this.xmlBlasterAccess.getGlobal();
      this.timeout = timeout;
      String appletId = req.getRemoteAddr() + "-" + loginName + "-" + sessionId;
      this.ME  = "PushHandler-" + appletId;

      // Setting HTTP headers to prevent caching
      /* !!! activate when migrating to servlet 2.2 !!!
      res.addHeader("Expires", "Tue, 31 Dec 1997 23:59:59 GMT");
      res.addHeader("Cache-Control", "no-cache");
      res.addHeader("Pragma", "no-cache");
      */

      this.outMulti =  this.res.getOutputStream();
      this.res.setContentType("multipart/x-mixed-replace;boundary=End");
      outMulti.println();
      outMulti.println("--End");
   }

   /**
    * @param userData
    * @see org.xmlBlaster.util.I_Timeout
    */
   public void timeout(Object userData) {
      if (log.isLoggable(Level.FINER)) log.finer("Pinging applet ...");
      this.pingCounter++;
      try {
         if (this.missingPongs > 2) {
            // Allow three pongs delay over slow connections
            log.warning("Applet seems to have disappeared, no response for my ping=" + pingCounter +
                         ", missing " + this.missingPongs + " responses. Closing connection.");
            cleanup();
         }
         else {
            String text = "refresh-" + pingCounter;
            if (log.isLoggable(Level.FINE)) log.fine("Sending ping '" + text + "'  to applet, missingPongs=" + this.missingPongs + " ...");
            ping(text);
            this.missingPongs++;
         }
      } catch(Exception e) {
         //error handling: applet closed connection.
         log.warning("We tried to ping=" + pingCounter + " an applet who is not interested. Close PushHandler.");
         cleanup();
      }

      synchronized (this) {
         if (this.pingInterval > 0)
            this.pingTimeoutHandle = this.timeout.addTimeoutListener(this, this.pingInterval, userData);
      }
   }

   public void startPing() throws XmlBlasterException {
      log.fine("startPing ...");
      setPingInterval(this.pingInterval);
      ping("refresh");
   }

   public void stopPing() throws XmlBlasterException {
      log.fine("stopPing ...");
      setPingInterval(0L);
   }

   public boolean isClosed() {
      return this.closed;
   }

   /**
    * Set or change the ping interval.
    * <p>
    * Ping the applet to avoid that the web server or a internet proxy
    * closes the http connection after a vendor specific timeout.
    * <br />
    * Note that the ping sends some bytes as well, a proxy
    * may close the http connection if the amount of bytes per second
    * falls below a certain level.
    * <br />
    * The applet responses with 'pong' which allows us to check if the applet
    * is still here.
    * </p>
    * @param pingInterval in milli seconds (defaults to 10000)
    */
   public synchronized void setPingInterval(long pingInterval) throws XmlBlasterException {
      this.pingInterval = pingInterval;
      if (this.pingInterval < 1) {
         if (this.pingTimeoutHandle != null) {
            this.timeout.removeTimeoutListener(this.pingTimeoutHandle);
         }
         return;
      }
      this.pingTimeoutHandle = this.timeout.addOrRefreshTimeoutListener(this,
               this.pingInterval, null, this.pingTimeoutHandle);
   }
   public I_XmlBlasterAccess getXmlBlasterAccess() {
      return this.xmlBlasterAccess;
   }

   /**
    * Don't forget to call this method when you want to close the connection.
    */
   public void shutdownAppletConnection() {
      try {
         this.closed = true;
         stopPing();
         if (this.outMulti != null) this.outMulti.close();
         log.info("Closed push connection to applet");
      }
      catch(Exception e) {
         e.printStackTrace();
         log.severe("Error occurred while de-initializing the push handler :"+e.toString());
      }
   }

   /**
    * If you implement I_ProxyInterceptor and register it here,
    * your update() implementation is called and may manipulate the
    * received message from xmlBlaster before it is sent to the applet.
    * @param interceptor Your optional implementation
    */
   public void setProxyInterceptor(I_Callback interceptor) {
      this.callbackInterceptor = interceptor;
   }

   /**
    * Shutdown applet connection and xmlBlaster connection.
    */
   public void cleanup() {
      if (log.isLoggable(Level.FINER)) log.finer("Entering cleanup() ...");
     
      if (this.xmlBlasterAccess != null) {
         try {
            this.xmlBlasterAccess.disconnect(null);
            log.info("XmlBlaster connection removed");
            this.xmlBlasterAccess = null;
         }
         catch (Exception e) {
            e.printStackTrace();
            log.severe("Can't destroy http connection: " + e.toString());
         }
      }

      this.callbackInterceptor = null;

      try {
         shutdownAppletConnection();
      }
      catch (Exception e) {
         e.printStackTrace();
         log.severe("Can't destroy http connection: " + e.toString());
      }
   }

   /**
    * Pushing a message to the applet.
    * @param chunk The raw data, we encode it with base64, the applet must know how to handle it.
    */
   private void pushToApplet(byte[] chunk) throws IOException {
      if (log.isLoggable(Level.FINE)) log.fine("Pushing multipart for applet, size=" + chunk.length);
      if (log.isLoggable(Level.FINE)) log.fine("Pushing multipart for applet, content='" + new String(chunk) + "'");
     
      String base64 = Base64.encode(chunk);
      if (log.isLoggable(Level.FINE)) log.fine("Pushing multipart for applet, content (encoded)='" + new String(base64) + "'");
      synchronized(outMulti) {
         outMulti.println(base64);
         outMulti.println("--End");
         outMulti.flush();
         log.fine("Pushed data successfully as multipart to applet.");
      }
   }

   /**
    * Pushes received message back to browser or applet.
    * <p>
    * The callbacks are java-serialized Maps for key/qos etc.
    * </p>
    * <pre>
    * Format:  Serialized
    *      "'update' sessionId QoS Key Content"
    * and then Base64 encoded
    * </pre>
    */
   public String update(String sessionId, UpdateKey updateKey, byte[] content, UpdateQos updateQos) {
      try {
         if (log.isLoggable(Level.FINE)) log.fine("update '" + updateKey.getOid() + "'");

         if(callbackInterceptor != null) {
            callbackInterceptor.update(sessionId, updateKey, content, updateQos);
         }

         ByteArrayOutputStream dump = new ByteArrayOutputStream(1024);
         ObjectOutputStreamMicro out = new ObjectOutputStreamMicro(dump);

         out.writeObject(I_XmlBlasterAccessRaw.UPDATE_NAME.toString()); // "update"
         out.writeObject(sessionId);

         Hashtable qosMap = updateQos.getData().toJXPath();
         out.writeObject(qosMap);

         Hashtable keyMap = updateKey.getData().toJXPath();
         out.writeObject(keyMap);

         out.writeObject(Base64.encode(content));

         pushToApplet(dump.toByteArray());
         if (log.isLoggable(Level.FINE)) log.fine("Sent update message '" + updateKey.getOid() + "' content='" + new String(content) + "' to applet");
      }
      catch(Exception e) {
         e.printStackTrace();
         log.severe(e.toString());
      }
      return "<qos/>"; // TODO: Async wait on return value from browser/applet
   }

   /**
    * calls the ping method in the parentframe of the callback frame
    * The data must be Javascript code
    * @param state The string "refresh"<br />
    *              When login is done successfully, state="loginSucceeded" is sent
    *              one time
    */
   public void ping(String state) throws XmlBlasterException {
      try {
         ByteArrayOutputStream dump = new ByteArrayOutputStream(1024);
         ObjectOutputStreamMicro out = new ObjectOutputStreamMicro(dump);

         out.writeObject(I_XmlBlasterAccessRaw.PING_NAME); // "ping"
         out.writeObject("<qos id='"+state+"'/>");

         pushToApplet(dump.toByteArray());
         if (log.isLoggable(Level.FINE)) log.fine("Sent ping '" + state + "' to applet");
      }
      catch (IOException e) {
         throw XmlBlasterException.convert(glob, ErrorCode.RESOURCE_UNAVAILABLE, ME, "ping(" + state + ") failed", e);
      }
   }

   /**
    * This is the browser response for our previous ping.
    */
   public void pong() {
      this.missingPongs = 0;
   }
}
TOP

Related Classes of org.xmlBlaster.protocol.http.appletproxy.PushHandler

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.