Package org.coweb

Source Code of org.coweb.SessionModerator

package org.coweb;

import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicInteger;

import java.util.logging.Logger;

import org.cometd.bayeux.Message;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.LocalSession;
import org.cometd.bayeux.server.ServerSession;

/**
*
* In order to use a custom SessionModerator for an OCW application, you must
* <a href="http://opencoweb.org/ocwdocs/java/deploy.html#configuring-coweb-options"> use a custom coweb configuration</a>
* (WEB-INF/cowebConfig.json).
*
* <p>In your custom coweb configuration, set both `moderatorIsUpdater` and
* `operationEngine` to "true". Set `sessionModerator` to the full java class
* name (e.g. "org.coweb.DefaultSessionModerator").
*
* <p>Note that the users of this class make no guarantee about the number of
* threads that might operate on a SessionModerator object. Thus, implementors
* of SessionModerator subclasses must ensure the thread safety of the
* implementation. For example, the {@link SessionModerator#onSync} method
* likely needs to be declared synchronized.
*
* The current API has four methods accepting an org.cometd.bayeux.Message
* argument. Since it is not immediately apparent what information would be
* useful to application developers, we do not split up the message like we
* do for onServiceResponse. Until it becomes clear how to send specific
* information that is encoded inside the message, the API will continue to
* pass a message, but developers should be aware that the format of the message
* is not guaranteed to change with new releases.
*
*/
public abstract class SessionModerator {

  private static final Logger log = Logger.getLogger(SessionHandler.class
      .getName());

  /**
    * The default SessionModerator implementation used by
    * {@link org.coweb.SessionModerator#getInstance}.
    */
  private static final String DefaultImpl =
    "org.coweb.DefaultSessionModerator";

  /* Each cowebkey has one SessionModerator. */
  private static HashMap<String, SessionModerator> instancesMap =
    new HashMap<String, SessionModerator>();

  protected SessionHandler sessionHandler = null;

  /* Upon creating a LocalSession, a respective ServerSession object is
   * created. Make sure both have the same attributes set: use
   * {@link SessionModerator#setSessionAttribute}.
   */
  protected LocalSession localSession = null;
  protected ServerSession serverSession = null;

  private Set<CollabInterface> collabInterfaces;

  /**
    * Use {@link SessionModerator#getInstance} to obtain a SessionModerator
    * object.
    */
  protected SessionModerator() {
    ;
  }

  /**
    * Use {@link SessionModerator#getInstance} to obtain a SessionModerator
    * object.
    */
  protected SessionModerator(SessionHandler sessionHandler) {

  }

  /**
    * Returns the SessionModerator instance for a given confKey.
    * The session moderator is either 1) given by the parameter classStr or
    * 2) SessionModerator.DefaultImpl if classStr is null.
    *
    * <p>If no SessionModerator exists for the confKey, a new instance is
    * created and initialized. The sessionid attribute is always updated to
    * that of sessionHandler.
    *
    * <p>The implicit assumption is that there exists a one-to-one
    * correspondence between SessionHandler
    * objects and confKeys.
    *
    * @param sessionHandler used to initialize the newly created
    *        SessionModerator, if one was created
    * @param classStr SessionModerator implementation to create
    * @param confKey cowebkey
    * @return  null if the SessionModerator class fails to be constructed or
    *         initialized.
    */
  public static synchronized SessionModerator getInstance(
      SessionHandler sessionHandler, String classStr, String confKey) {

    SessionModerator mod = SessionModerator.instancesMap.get(confKey);
    if (null == mod) {
      if (classStr == null) {
        classStr = SessionModerator.DefaultImpl;
      }

      try {
        Class<? extends SessionModerator> c = Class.forName(classStr)
            .asSubclass(SessionModerator.class);
        mod = c.newInstance();
        mod.init(sessionHandler);
        SessionModerator.instancesMap.put(confKey, mod);
      } catch (Exception e) {
        e.printStackTrace();
      }
    } else {
      mod.updateSessionHandler(sessionHandler);
    }

    return mod;
  }

  /**
    * Creates a bayeux server-client pair for this session moderator. Both
    * the LocalSession and ServerSession objects are set here.
    */
  private void init(SessionHandler sessionHandler) {
    this.collabInterfaces = new HashSet<CollabInterface>();

    this.sessionHandler = sessionHandler;
    BayeuxServer server = SessionManager.getInstance().getBayeux();

    String sessionId = sessionHandler.getSessionId();
    this.localSession = server.newLocalSession(sessionId);
    this.localSession.setAttribute("sessionid", sessionId);
    this.localSession.handshake();
    this.serverSession = this.localSession.getServerSession();
    this.setSessionAttribute("sessionid", sessionId);
    this.setSessionAttribute("username", "moderator");
  }

  /**
    * Updates the LocalSession and ServerSession attributes given a
    * SessionHandler.
    *
    * <p>Visibility is default - only package level should access it.
    *
    * @param handler determines attributes to update
    */
  void updateSessionHandler(SessionHandler handler) {
    this.sessionHandler = handler;
    this.setSessionAttribute("sessionid", handler.getSessionId());
  }

  /**
    * Returns the associated LocalSession object - this represents the
    * moderator when considered a "client" on the server. All messages
    * originating from the moderator should come <b>from</b> the LocalSession.
    *
    * @return the associated LocalSession
    */
  public LocalSession getLocalSession() {
    return this.localSession;
  }

  /**
    * Returns the associated ServerSession object. Any messages sent <b>to</b>
    * the moderator will have this ServerSession object as the recipient.
    *
    * @return the associated ServerSession
    */
  public ServerSession getServerSession() {
    return this.serverSession;
  }

  /**
    * A SessionModerator is special - it has an associated ServerSession like
    * all "clients," but also has a LocalSession. Attributes will typically be
    * synchronized. so use this method to set an attribute in both Session
    * objects.
    *
    * <p>For any attributes that SHOULD not be shared, use getLocalSession or
    * getServerSession and set the attribute on that object only.
    *
    * @param key attribute key
    * @param val attribute value
    * @see org.cometd.bayeux.Session#setAttribute
    */
  public void setSessionAttribute(String key, Object val) {
    this.localSession.setAttribute(key, val);
    this.serverSession.setAttribute(key, val);
  }

  /**
   * The coweb server calls this anytime the server’s local operation engine
   * determines there is a sync event to apply to the moderator’s local copy
   * of the application data structure(s). Like coweb browser applications,
   * the implementors must honor and apply all sync events to local data
   * structure(s).
   *
   * <p>This method should return whether or not the sync event should be
   * forwarded to bots.
   *
   * <p>The parameter data has the five keys specified below.
   *     <li> topic - A string specifying the coweb topic for which the message
   *     was sent. This is useful to distinguish browser collab objects and
   *     sendSync topic names that operations are sent on.
   *     <li> type - String specifying the type of sync. This can be one of
   *     {insert, delete, update, null}.
   *     <li> site - Integer site ID where the event originated.
   *     <li> value - JSON object value representing the new value. See
   *     org.eclipse.jetty.util.ajax.JSON for how to read this object.
   *     <li> position - Integer position specifying where in the
   *     one-dimensional array the operation should be applied.
   *
   * @param clientId string identifier of client
   * @param data Map with sync data as described above.
   */
  public abstract void onSync(String clientId, Map<String, Object> data);

  /**
   * Return a mapping of collab element IDs to application state. The coweb
   * server calls this when a new client joins a coweb sessiob. The
   * <i>moderatorIsUpdater</i> boolean configuration option must be set to
   * true, * so that the server knows to call this method (otherwise other
   * clients are late join updaters).
   *
   * <p>The function should return a (key, value) map where there is one key
   * for each collab object in the coweb application. The key should be the
   * collab object ID, and the associated value should be a JSON object
   * representing the state of that collab object.
   *
   * <p>For example, for a conference session with two collaborative elements
   * ("foo" and "bar"), this method will return a map with the pairs ("foo",
   * fooStateObj) and ("bar", barStateObj).
   *
   * <p>null should *not* be returned under any circumstance.
   *
   * @return collab application state map
   */
  public abstract Map<String, Object> getLateJoinState();

  /**
   * Determines whether or not a connecting client can join a session.
   *
   * @param clientId string identifier of client
   * @param userDefined arbitrary data sent by the coweb application. See the
   *        org.eclipse.jetty.util.ajax.JSON class.
   * @return whether or not the client can join
   */
  public abstract boolean canClientJoinSession(String clientId,
      Map<String, Object> userDefined);

  /**
   * Called to notify this moderator when a client has subscribed to updates.
   *
   * @param clientId string identifier of client
   */
  public abstract void onClientJoinSession(String clientId);

  /**
   * Called to notify this moderator that a client has left the session.
   *
   * @param clientId string identifier of client
   */
  public abstract void onClientLeaveSession(String clientId);

  /**
   * Should determine whether or not a client can subscribe to bot messages.
   *
   * @param svcName service name
   * @param clientId string identifier of client
   * @return whether or not client can subscribe to bot messages
   */
  public abstract boolean canClientSubscribeService(String svcName,
      String clientId);

  /**
   * Should determine whether or not a client can publish messages to bots.
   *
   * @param svcName service name
   * @param clientId string identifier of client
   * @param botData Data intended to be sent to the bot. See the
   *        org.eclipse.jetty.util.ajax.JSON class.
   * @return whether or not the client can publish
   */
  public abstract boolean canClientMakeServiceRequest(String svcName,
      String clientId, Map<String, Object> botData);

  /**
   * Called whenever a bot responds to a service message sent by this
   * moderator.
   * @param svcName The bot's name.
   * @param data Bot message data as a JSON encodable map. Might be null.
   * @param error Was there an error?
   * @param isPublic Was the message a public bot broadcast?
   */
  public abstract void onServiceResponse(String svcName,
      Map<String, Object> data, boolean error, boolean isPublic);

  /**
   * Called whenever a session is over (i.e.&nbsp;all clients have left).
   * Note that this SessionModerator object will still be kept in memory if
   * moderatorIsUpdater and reused for any future coweb sessions with the
   * same cowebkey.
   *
   * <p>All CollabInterface objects created prior to onSessionEnd() being
   * called are now invalid and can no longer be used.
   *
   * <p>If this moderator is not the updater, it is recommended that
   * subclasses use this method to help in resetting application state to a
   * fresh state incase a new session is initiated with the same cowebkey.
   * Otherwise, the browser clients will be out of sync with the moderator's
   * state.
   *
   * <p>This is important, because once a moderator is created for a specific
   * cowebkey, it is never destroyed, even if the session is ended.
   */
  public abstract void onSessionEnd();

  /**
   * Called when all clients have left a session. This method invokes the
   * onSessionEnd() callback.
   */
  void endSession() {
    for (CollabInterface ci: this.collabInterfaces) {
      this.serverSession.removeListener(ci);
    }
    this.collabInterfaces.clear();
    this.sessionHandler = null;
    this.onSessionEnd();
  }

  /**
   * Callback when this a session has been created and joined by at least one
   * other external client. Note that this may be called multiple times in the
   * lifetime of a SessionModerator, because a moderator persists even when
   * all clients leave a session. When a new client joins a session that
   * already existed beforehand, this method will be called to notify the
   * moderator that the session is now "active" again.
   */
  public abstract void onSessionReady();

  /**
   * Create a CollabInterface for use with this moderator. This method should
   * only be called when onSessionReady() has been invoked more recently than
   * onSessionEnd().
   *
   * <p>Once onSessionEnd() has been invoked, all CollabInterface
   * objects become invalid, and new ones can only be created once the session
   * becomes active again (i.e. onSessionReady() is invoked again).
   *
   * @param collabId Identifier for this collaborative object.
   */
  public CollabInterface initCollab(String collabId) {
    CollabInterface ci = new CollabInterface(this, collabId);
    this.serverSession.addListener(ci);
    this.collabInterfaces.add(ci);
    return ci;
  }

  /**
   * Provide a simple interface for sending collaborative messages. This
   * provides similar functionality to the JavaScript CollabInterface; since
   * SessionModerator provides much of the functionality of the JavaScript
   * CollabInterface, this Java CollabInterface only provides methods to send
   * data. Receiving data is handled by the moderator.
   */
  public static class CollabInterface
      implements ServerSession.MessageListener {

    private SessionModerator moderator;
    private String collabId;
    private AtomicInteger serviceId;

    /**
     * Create a collaborative object interface for sending collab messages
     * to other clients in an OCW session.
     * @param collabId Identifier for this collaborative object.
     */
    private CollabInterface(SessionModerator mod, String collabId) {
      this.moderator = mod;
      this.collabId = collabId;
      this.serviceId = new AtomicInteger(0);
    }

    public void subscribeService(String svcName) {
      this.moderator.sessionHandler.subscribeModeratorToService(svcName);
    }

    public boolean onMessage(ServerSession to, ServerSession from,
        ServerMessage message) {
      if (ServiceHandler.isServiceMessage(message)) {
        String svcName = ServiceHandler.getServiceNameFromMessage(
            message);

        Map<String, Object> data = message.getDataAsMap();
        Boolean error = (Boolean)data.get("error");
        if (null == error)
          error = false;
        data = (Map<String, Object>)data.get("value");

        /* No guarantees that data is not null. */
        this.moderator.onServiceResponse(svcName, data, error,
            ServiceHandler.isPublicBroadcast(message));
      } else {
        log.warning("CollabInterface received message it doesn't " +
            "understand: " + message);
      }
      return true;
    }

    /**
     * Send an application sync event.
     * @param name Which application property changed.
     * @param value New property value, JSON encodable.
     * @param type One of {"insert", "delete", "update", null}
     * @param position Position of the value change.
     */
    public void sendSync(String name, Object value, String type,
        int position) {
      name = "coweb.sync." + name + "." + this.collabId;
      this.moderator.sessionHandler.publishModeratorSync(name, value,
          type, position);
    }

    /**
     * Send a message to a service bot.
     * @param service Bot service name.
     * @param params Bot message, JSON encodable.
     */
    public void postService(String service, Map<String, Object> params) {
      /* Need atomicity for serviceId counter. */
      int id = this.serviceId.getAndIncrement();
      String topic = "coweb.service.request." + service + "_" + id +
        "." + this.collabId;
      this.moderator.sessionHandler.postModeratorService(service, topic,
          params);
    }

  }

}
TOP

Related Classes of org.coweb.SessionModerator

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.