Package org.persvr.remote

Source Code of org.persvr.remote.EventStream$Notification

package org.persvr.remote;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.persvr.data.GlobalData;
import org.persvr.data.ObjectId;
import org.persvr.data.ObservedCall;
import org.persvr.data.PersistableObject;
import org.persvr.data.PropertyChangeSetListener;
import org.persvr.javascript.PersevereContextFactory;
import org.persvr.remote.Client.IndividualRequest;
import org.persvr.security.UserSecurity;


/**
* This represents a stream of events. You can subscribe to the event stream with addCallback and
* publish events with the fire method.
* @author Kris
*
*/
public abstract class EventStream implements PropertyChangeSetListener {
  EventCallback callback;
  public Object authorizedUser;

  HttpServletResponse response;
  public static Map<String, Client> streams = new HashMap<String, Client>();
  public static int getConnectionCount(){
    int connectionCount = 0;
    synchronized(streams){
      for(Client client : streams.values()){
        if(client.callback != null)
          connectionCount++;
      }
    }
    return connectionCount;
  }
  protected boolean finished = false;
  /**
   * This is how often we ping the connection, in seconds. This SHOULD NOT be necessary, but I haven't determined how to detect a connection close in Jetty yet
   */
  static final int CONNECTION_MONITOR_INTERVAL = 30;

  Map<Map<ObjectId, Set<String>>, Map<String,String>> readSetToHeaders = new HashMap<Map<ObjectId, Set<String>>, Map<String,String>>();
  public void addSubscription(Map<String,String> headers) {
    readSetToHeaders.put(PersistableObject.getReadSet(),headers);
  }
  public void removeSubscription(Map<String,String> headers) {
    String pathInfo = headers.get("__pathInfo__");
    Set<Map<ObjectId, Set<String>>> removalSet = new HashSet<Map<ObjectId, Set<String>>>();
     for (Entry<Map<ObjectId, Set<String>>, Map<String,String>> entry : readSetToHeaders.entrySet())  {
      if (pathInfo.equals(entry.getValue().get("__pathInfo__")))
        removalSet.add(entry.getKey());
    }
     for (Map<ObjectId, Set<String>> readSet : removalSet)
       readSetToHeaders.remove(readSet);
  }
  /**
   * Adds a callback to will be called when a notification is sent through this event stream
   * @param callback
   */
  public synchronized void addCallback(EventCallback callback) {
    this.callback = callback;
    Notification response;
    if ((response= notificationQueue.peek()) != null) {
      try {
        fire(null);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }
  public boolean isConnected(){
    return callback != null;
  }
  /**
   * Indicates an event is available on this stream
   * @return
   */
  public boolean eventAvailable() {
    return notificationQueue.peek() != null;
  }
  public synchronized void removeCallback() {
    this.callback = null;
  }
/*  List<EventStream> subStreams = new LinkedList(); // this is to keep a strong reference to substreams, so event listeners can be added to and strong references can be maintained
  public void addSubEventStream(EventStream eventStream) {
    subStreams.add(eventStream);
    eventStream.addCallback(new EventCallback() {// direct all the event streams through a single one
      public void onEvent(InnerResponse response) throws IOException {
        EventStream.this.fire(response);
      }
    });
  }*/
  @Override
  public String toString() {
    // TODO Auto-generated method stub
    return super.toString() + "list ";// + subStreams;
  }
  BlockingQueue<Notification> notificationQueue = new LinkedBlockingQueue<Notification>();
  public Notification take(int timeoutInSeconds) {
    try{
      return notificationQueue.poll(timeoutInSeconds,TimeUnit.SECONDS);
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
  }
  public interface EventCallback{
    void onEvent() throws IOException;
  }
  /** A call notification combined with necessary request context information
   * @author Kris
   */
  public static class Notification {
    Map<String,String> headers;
    ObservedCall call;
    String pathInfo;
    public Map<String, String> getHeaders() {
      return headers;
    }
    public ObservedCall getCall() {
      return call;
    }
    public String getPathInfo() {
      return pathInfo;
    }
  }
  /**
   * this is called by notification system and adds an event to the stream. This is expected
   * to be a new thread and will adapt to current thread
   */
  public void propertyChange(List<ObservedCall> evts) {
    if (finished) // if it is finished it should be gc'ed soon, but until then, we don't want it doing anything
      return;
    UserSecurity.registerThisThread(authorizedUser);
    IndividualRequest request = Client.getCurrentObjectResponse();
    ((Client)this).adoptThread(Thread.currentThread());
    boolean channelFound = false;
    // we map the resource changes here, so each resource only fires once
    List<Notification> updatedResources = new ArrayList<Notification>();
    for (ObservedCall evt : evts) {
      if (evt.getExcludedClient() != this) { // if it is an event that we caused, we don't need to fire a notification
        Notification notification = new Notification();
        notification.call = evt;
        /*for (Entry<Map<ObjectId, Set<String>>, Map<String,String>> entry : readSetToHeaders.entrySet()){
          Map<ObjectId, Set<String>> readSet = entry.getKey();
          Set<String> propertySet = readSet.get(evt.getSourceId());
          if (propertySet != null) {
            Map<String,String> headers = entry.getValue();
            notification.headers = headers;
            if (propertySet == FullSet.instance && !((Persistable)evt.getSource() instanceof List)) {
              String pathInfo = evt.getSourceId().toString();
              notification.pathInfo = pathInfo;
              updatedResources.add(notification); // TODO: I think we can just addd this directly to the notificationQueue
            }
            else {
              channelFound = true;
              String pathInfo = headers.get("__pathInfo__");
              notification.pathInfo = pathInfo;
              updatedResources.add(notification);
            }
            break;
          }
        }*/
        if (!channelFound) {
          notification.headers = new HashMap<String,String>();
          String pathInfo = evt.getSourceId().toString();
          notification.pathInfo = pathInfo;
          updatedResources.add(notification);
        }
      }     
    }
    if(notificationQueue.size() > 100){
      notificationQueue.clear();
    }
    // go through each resource now
    for (Notification notification: updatedResources) {
      notificationQueue.add(notification);
//      createResponse(entry.getKey(), notification.call);
      notification.headers.put("__now__", new Date().getTime() + "");
      /*if (!(response instanceof InnerResponse)) {
        finished = true;
        return;
      }*/
    }
    try {
      synchronized(this) { // inlined from fire
        if (callback != null && notificationQueue.peek() != null)
          callback.onEvent();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    Client.threadClient.set(request);
   
  }
/*  protected void createResponse(String pathInfo, ObservedCall call) {
    try {
      response = response == null || response instanceof InnerResponse ?
          new InnerResponse('/' + pathInfo) : response; // TODO: not sure if this is how we want to do the slash prefix
        if (!(response instanceof InnerResponse))
          response.setStatus(202);
        response.setHeader("Event", call.getMethod());
        IndividualRequest request = ((ClientConnection)this).getIndividualRequest(null);
        request.setRequestedPath(pathInfo, Identification.idForString(pathInfo));
//      target = PersevereServlet.handleRange(range, target, since,response);
      String output = request.getValueString(call.getContent(),true);
      response.setHeader("Last-Modified", "" + new Date()); // TODO: This should come from the transaction
        response.getOutputStream().print(output);
    } catch (IOException e) {
      e.printStackTrace();
    }
    responseQueue.add(response instanceof InnerResponse ? (InnerResponse) response : new InnerResponse(null)); 

  }*/

  public void setResponse(HttpServletResponse response) {
    this.response = response;
  }
  public boolean started = false;
  public String connectionId;
  public int bytesSent = 0;


  public void finished() {
    //System.err.println("finished "+ connectionId);   
    finished = true;
    synchronized(streams){
      streams.remove(connectionId);// remove the stream, this should make it gc-able.
    }
    Scriptable global = GlobalData.getGlobalScope();
    Function onDisconnect = (Function) global.get("onDisconnect", global);

    if(started){
     
      onDisconnect.call(PersevereContextFactory.getContext(), global, global, new Object[]{ this });
    }
  }


  public synchronized void fire(Notification response) throws IOException {
    try {
      if (response != null)
        notificationQueue.add(response);
      synchronized(this) {
        if (callback != null && (response = notificationQueue.peek()) != null)
          callback.onEvent();
      }
    } catch (IOException e) {
      Log log = LogFactory.getLog(EventStream.class);
      log.info("unable to send event ", e);
      finished();
    }
  }




}
TOP

Related Classes of org.persvr.remote.EventStream$Notification

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.