text:
package org.apache.zeppelin.socket;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gson.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.zeppelin.conf.ZeppelinConfiguration;
import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
import org.apache.zeppelin.display.AngularObject;
import org.apache.zeppelin.display.AngularObjectRegistry;
import org.apache.zeppelin.display.AngularObjectRegistryListener;
import org.apache.zeppelin.helium.ApplicationEventListener;
import org.apache.zeppelin.helium.HeliumPackage;
import org.apache.zeppelin.interpreter.Interpreter;
import org.apache.zeppelin.interpreter.InterpreterContextRunner;
import org.apache.zeppelin.interpreter.InterpreterGroup;
import org.apache.zeppelin.interpreter.InterpreterResult;
import org.apache.zeppelin.interpreter.InterpreterResultMessage;
import org.apache.zeppelin.interpreter.InterpreterSetting;
import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener;
import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion;
import org.apache.zeppelin.json.NotebookTypeAdapterFactory;
import org.apache.zeppelin.notebook.JobListenerFactory;
import org.apache.zeppelin.notebook.Folder;
import org.apache.zeppelin.notebook.Note;
import org.apache.zeppelin.notebook.Notebook;
import org.apache.zeppelin.notebook.NotebookAuthorization;
import org.apache.zeppelin.notebook.NotebookEventListener;
import org.apache.zeppelin.notebook.Paragraph;
import org.apache.zeppelin.notebook.ParagraphJobListener;
import org.apache.zeppelin.notebook.ParagraphRuntimeInfo;
import org.apache.zeppelin.notebook.repo.NotebookRepo.Revision;
import org.apache.zeppelin.notebook.socket.Message;
import org.apache.zeppelin.notebook.socket.Message.OP;
import org.apache.zeppelin.notebook.socket.WatcherMessage;
import org.apache.zeppelin.rest.exception.ForbiddenException;
import org.apache.zeppelin.scheduler.Job;
import org.apache.zeppelin.scheduler.Job.Status;
import org.apache.zeppelin.server.ZeppelinServer;
import org.apache.zeppelin.ticket.TicketContainer;
import org.apache.zeppelin.types.InterpreterSettingsList;
import org.apache.zeppelin.user.AuthenticationInfo;
import org.apache.zeppelin.util.WatcherSecurityKey;
import org.apache.zeppelin.utils.InterpreterBindingUtils;
import org.apache.zeppelin.utils.SecurityUtils;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.quartz.SchedulerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Queues;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;

/**
 * Zeppelin websocket service.
 */
public class NotebookServer extends WebSocketServlet
    implements NotebookSocketListener, JobListenerFactory, AngularObjectRegistryListener,
    RemoteInterpreterProcessListener, ApplicationEventListener {

  /**
   * Job manager service type
   */
  protected enum JOB_MANAGER_SERVICE {
    JOB_MANAGER_PAGE("JOB_MANAGER_PAGE");
    private String serviceTypeKey;

    JOB_MANAGER_SERVICE(String serviceType) {
      this.serviceTypeKey = serviceType;
    }

    String getKey() {
      return this.serviceTypeKey;
    }
  }

  private static final Logger LOG = LoggerFactory.getLogger(NotebookServer.class);
  Gson gson = new GsonBuilder()
      .registerTypeAdapterFactory(new NotebookTypeAdapterFactory<Paragraph>(Paragraph.class) {
        @Override
        protected void beforeWrite(Paragraph source, JsonElement toSerialize) {
          Map<String, ParagraphRuntimeInfo> runtimeInfos = source.getRuntimeInfos();
          if (runtimeInfos != null) {
            JsonElement jsonTree = gson.toJsonTree(runtimeInfos);
            if (toSerialize instanceof JsonObject) {
              JsonObject jsonObj = (JsonObject) toSerialize;
              jsonObj.add("runtimeInfos", jsonTree);
            }
          }
        }
      }).setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create();
  final Map<String, List<NotebookSocket>> noteSocketMap = new HashMap<>();
  final Queue<NotebookSocket> connectedSockets = new ConcurrentLinkedQueue<>();
  final Map<String, Queue<NotebookSocket>> userConnectedSockets = new ConcurrentHashMap<>();

  /**
   * This is a special endpoint in the notebook websoket, Every connection in this Queue
   * will be able to watch every websocket event, it doesnt need to be listed into the map of
   * noteSocketMap. This can be used to get information about websocket traffic and watch what
   * is going on.
   */
  final Queue<NotebookSocket> watcherSockets = Queues.newConcurrentLinkedQueue();

  private Notebook notebook() {
    return ZeppelinServer.notebook;
  }

  @Override
  public void configure(WebSocketServletFactory factory) {
    factory.setCreator(new NotebookWebSocketCreator(this));
  }

  public boolean checkOrigin(HttpServletRequest request, String origin) {
    try {
      return SecurityUtils.isValidOrigin(origin, ZeppelinConfiguration.create());
    } catch (UnknownHostException e) {
      LOG.error(e.toString(), e);
    } catch (URISyntaxException e) {
      LOG.error(e.toString(), e);
    }
    return false;
  }

  public NotebookSocket doWebSocketConnect(HttpServletRequest req, String protocol) {
    return new NotebookSocket(req, protocol, this);
  }

  @Override
  public void onOpen(NotebookSocket conn) {
    LOG.info("New connection from {} : {}", conn.getRequest().getRemoteAddr(),
        conn.getRequest().getRemotePort());
    connectedSockets.add(conn);
  }

  @Override
  public void onMessage(NotebookSocket conn, String msg) {
    Notebook notebook = notebook();
    try {
      Message messagereceived = deserializeMessage(msg);
      LOG.debug("RECEIVE << " + messagereceived.op);
      LOG.debug("RECEIVE PRINCIPAL << " + messagereceived.principal);
      LOG.debug("RECEIVE TICKET << " + messagereceived.ticket);
      LOG.debug("RECEIVE ROLES << " + messagereceived.roles);

      if (LOG.isTraceEnabled()) {
        LOG.trace("RECEIVE MSG = " + messagereceived);
      }

      String ticket = TicketContainer.instance.getTicket(messagereceived.principal);
      if (ticket != null && !ticket.equals(messagereceived.ticket)) {
        /* not to pollute logs, log instead of exception */
        if (StringUtils.isEmpty(messagereceived.ticket)) {
          LOG.debug("{} message: invalid ticket {} != {}", messagereceived.op,
              messagereceived.ticket, ticket);
        } else {
          if (!messagereceived.op.equals(OP.PING)) {
            conn.send(serializeMessage(new Message(OP.SESSION_LOGOUT).put("info",
                "Your ticket is invalid possibly due to server restart. "
                    + "Please login again.")));
          }
        }
        return;
      }

      ZeppelinConfiguration conf = ZeppelinConfiguration.create();
      boolean allowAnonymous = conf.isAnonymousAllowed();
      if (!allowAnonymous && messagereceived.principal.equals("anonymous")) {
        throw new Exception("Anonymous access not allowed ");
      }

      HashSet<String> userAndRoles = new HashSet<>();
      userAndRoles.add(messagereceived.principal);
      if (!messagereceived.roles.equals("")) {
        HashSet<String> roles =
            gson.fromJson(messagereceived.roles, new TypeToken<HashSet<String>>() {
            }.getType());
        if (roles != null) {
          userAndRoles.addAll(roles);
        }
      }
      if (StringUtils.isEmpty(conn.getUser())) {
        addUserConnection(messagereceived.principal, conn);
      }
      AuthenticationInfo subject =
          new AuthenticationInfo(messagereceived.principal, messagereceived.ticket);

      /** Lets be elegant here */
      switch (messagereceived.op) {
          case LIST_NOTES:
            unicastNoteList(conn, subject, userAndRoles);
            break;
          case RELOAD_NOTES_FROM_REPO:
            broadcastReloadedNoteList(subject, userAndRoles);
            break;
          case GET_HOME_NOTE:
            sendHomeNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case GET_NOTE:
            sendNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case NEW_NOTE:
            createNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case DEL_NOTE:
            removeNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case REMOVE_FOLDER:
            removeFolder(conn, userAndRoles, notebook, messagereceived);
            break;
          case MOVE_NOTE_TO_TRASH:
            moveNoteToTrash(conn, userAndRoles, notebook, messagereceived);
            break;
          case MOVE_FOLDER_TO_TRASH:
            moveFolderToTrash(conn, userAndRoles, notebook, messagereceived);
            break;
          case EMPTY_TRASH:
            emptyTrash(conn, userAndRoles, notebook, messagereceived);
            break;
          case RESTORE_FOLDER:
            restoreFolder(conn, userAndRoles, notebook, messagereceived);
            break;
          case RESTORE_NOTE:
            restoreNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case RESTORE_ALL:
            restoreAll(conn, userAndRoles, notebook, messagereceived);
            break;
          case CLONE_NOTE:
            cloneNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case IMPORT_NOTE:
            importNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case COMMIT_PARAGRAPH:
            updateParagraph(conn, userAndRoles, notebook, messagereceived);
            break;
          case RUN_PARAGRAPH:
            runParagraph(conn, userAndRoles, notebook, messagereceived);
            break;
          case PARAGRAPH_EXECUTED_BY_SPELL:
            broadcastSpellExecution(conn, userAndRoles, notebook, messagereceived);
            break;
          case RUN_ALL_PARAGRAPHS:
            runAllParagraphs(conn, userAndRoles, notebook, messagereceived);
            break;
          case CANCEL_PARAGRAPH:
            cancelParagraph(conn, userAndRoles, notebook, messagereceived);
            break;
          case MOVE_PARAGRAPH:
            moveParagraph(conn, userAndRoles, notebook, messagereceived);
            break;
          case INSERT_PARAGRAPH:
            insertParagraph(conn, userAndRoles, notebook, messagereceived);
            break;
          case COPY_PARAGRAPH:
            copyParagraph(conn, userAndRoles, notebook, messagereceived);
            break;
          case PARAGRAPH_REMOVE:
            removeParagraph(conn, userAndRoles, notebook, messagereceived);
            break;
          case PARAGRAPH_CLEAR_OUTPUT:
            clearParagraphOutput(conn, userAndRoles, notebook, messagereceived);
            break;
          case PARAGRAPH_CLEAR_ALL_OUTPUT:
            clearAllParagraphOutput(conn, userAndRoles, notebook, messagereceived);
            break;
          case NOTE_UPDATE:
            updateNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case NOTE_RENAME:
            renameNote(conn, userAndRoles, notebook, messagereceived);
            break;
          case FOLDER_RENAME:
            renameFolder(conn, userAndRoles, notebook, messagereceived);
            break;
          case UPDATE_PERSONALIZED_MODE:
            updatePersonalizedMode(conn, userAndRoles, notebook, messagereceived);
            break;
          case COMPLETION:
            completion(conn, userAndRoles, notebook, messagereceived);
            break;
          case PING:
            break; //do nothing
          case ANGULAR_OBJECT_UPDATED:
            angularObjectUpdated(conn, userAndRoles, notebook, messagereceived);
            break;
          case ANGULAR_OBJECT_CLIENT_BIND:
            angularObjectClientBind(conn, userAndRoles, notebook, messagereceived);
            break;
          case ANGULAR_OBJECT_CLIENT_UNBIND:
            angularObjectClientUnbind(conn, userAndRoles, notebook, messagereceived);
            break;
          case LIST_CONFIGURATIONS:
            sendAllConfigurations(conn, userAndRoles, notebook);
            break;
          case CHECKPOINT_NOTE:
            checkpointNote(conn, notebook, messagereceived);
            break;
          case LIST_REVISION_HISTORY:
            listRevisionHistory(conn, notebook, messagereceived);
            break;
          case SET_NOTE_REVISION:
            setNoteRevision(conn, userAndRoles, notebook, messagereceived);
            break;
          case NOTE_REVISION:
            getNoteByRevision(conn, notebook, messagereceived);
            break;
          case LIST_NOTE_JOBS:
            unicastNoteJobInfo(conn, messagereceived);
            break;
          case UNSUBSCRIBE_UPDATE_NOTE_JOBS:
            unsubscribeNoteJobInfo(conn);
            break;
          case GET_INTERPRETER_BINDINGS:
            getInterpreterBindings(conn, messagereceived);
            break;
          case SAVE_INTERPRETER_BINDINGS:
            saveInterpreterBindings(conn, messagereceived);
            break;
          case EDITOR_SETTING:
            getEditorSetting(conn, messagereceived);
            break;
          case GET_INTERPRETER_SETTINGS:
            getInterpreterSettings(conn, subject);
            break;
          case WATCHER:
            switchConnectionToWatcher(conn, messagereceived);
            break;
          default:
            break;
      }
    } catch (Exception e) {
      LOG.error("Can't handle message", e);
    }
  }

  @Override
  public void onClose(NotebookSocket conn, int code, String reason) {
    LOG.info("Closed connection to {} : {}. ({}) {}", conn.getRequest().getRemoteAddr(),
        conn.getRequest().getRemotePort(), code, reason);
    removeConnectionFromAllNote(conn);
    connectedSockets.remove(conn);
    removeUserConnection(conn.getUser(), conn);
  }

  private void removeUserConnection(String user, NotebookSocket conn) {
    if (userConnectedSockets.containsKey(user)) {
      userConnectedSockets.get(user).remove(conn);
    } else {
      LOG.warn("Closing connection that is absent in user connections");
    }
  }

  private void addUserConnection(String user, NotebookSocket conn) {
    conn.setUser(user);
    if (userConnectedSockets.containsKey(user)) {
      userConnectedSockets.get(user).add(conn);
    } else {
      Queue<NotebookSocket> socketQueue = new ConcurrentLinkedQueue<>();
      socketQueue.add(conn);
      userConnectedSockets.put(user, socketQueue);
    }
  }

  protected Message deserializeMessage(String msg) {
    return gson.fromJson(msg, Message.class);
  }

  protected String serializeMessage(Message m) {
    return gson.toJson(m);
  }

  private void addConnectionToNote(String noteId, NotebookSocket socket) {
    synchronized (noteSocketMap) {
      removeConnectionFromAllNote(socket); // make sure a socket relates only a
      // single note.
      List<NotebookSocket> socketList = noteSocketMap.get(noteId);
      if (socketList == null) {
        socketList = new LinkedList<>();
        noteSocketMap.put(noteId, socketList);
      }
      if (!socketList.contains(socket)) {
        socketList.add(socket);
      }
    }
  }

  private void removeConnectionFromNote(String noteId, NotebookSocket socket) {
    synchronized (noteSocketMap) {
      List<NotebookSocket> socketList = noteSocketMap.get(noteId);
      if (socketList != null) {
        socketList.remove(socket);
      }
    }
  }

  private void removeNote(String noteId) {
    synchronized (noteSocketMap) {
      List<NotebookSocket> socketList = noteSocketMap.remove(noteId);
    }
  }

  private void removeConnectionFromAllNote(NotebookSocket socket) {
    synchronized (noteSocketMap) {
      Set<String> keys = noteSocketMap.keySet();
      for (String noteId : keys) {
        removeConnectionFromNote(noteId, socket);
      }
    }
  }

  private String getOpenNoteId(NotebookSocket socket) {
    String id = null;
    synchronized (noteSocketMap) {
      Set<String> keys = noteSocketMap.keySet();
      for (String noteId : keys) {
        List<NotebookSocket> sockets = noteSocketMap.get(noteId);
        if (sockets.contains(socket)) {
          id = noteId;
        }
      }
    }

    return id;
  }

  private void broadcastToNoteBindedInterpreter(String interpreterGroupId, Message m) {
    Notebook notebook = notebook();
    List<Note> notes = notebook.getAllNotes();
    for (Note note : notes) {
      List<String> ids = notebook.getInterpreterSettingManager().getInterpreters(note.getId());
      for (String id : ids) {
        if (id.equals(interpreterGroupId)) {
          broadcast(note.getId(), m);
        }
      }
    }
  }

  private void broadcast(String noteId, Message m) {
    synchronized (noteSocketMap) {
      broadcastToWatchers(noteId, StringUtils.EMPTY, m);
      List<NotebookSocket> socketLists = noteSocketMap.get(noteId);
      if (socketLists == null || socketLists.size() == 0) {
        return;
      }
      LOG.debug("SEND >> " + m.op);
      for (NotebookSocket conn : socketLists) {
        try {
          conn.send(serializeMessage(m));
        } catch (IOException e) {
          LOG.error("socket error", e);
        }
      }
    }
  }

  private void broadcastExcept(String noteId, Message m, NotebookSocket exclude) {
    synchronized (noteSocketMap) {
      broadcastToWatchers(noteId, StringUtils.EMPTY, m);
      List<NotebookSocket> socketLists = noteSocketMap.get(noteId);
      if (socketLists == null || socketLists.size() == 0) {
        return;
      }
      LOG.debug("SEND >> " + m.op);
      for (NotebookSocket conn : socketLists) {
        if (exclude.equals(conn)) {
          continue;
        }
        try {
          conn.send(serializeMessage(m));
        } catch (IOException e) {
          LOG.error("socket error", e);
        }
      }
    }
  }

  private void multicastToUser(String user, Message m) {
    if (!userConnectedSockets.containsKey(user)) {
      LOG.warn("Multicasting to user {} that is not in connections map", user);
      return;
    }

    for (NotebookSocket conn : userConnectedSockets.get(user)) {
      unicast(m, conn);
    }
  }

  private void unicast(Message m, NotebookSocket conn) {
    try {
      conn.send(serializeMessage(m));
    } catch (IOException e) {
      LOG.error("socket error", e);
    }
    broadcastToWatchers(StringUtils.EMPTY, StringUtils.EMPTY, m);
  }

  public void unicastNoteJobInfo(NotebookSocket conn, Message fromMessage) throws IOException {
    addConnectionToNote(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(), conn);
    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    List<Map<String, Object>> noteJobs = notebook().getJobListByUnixTime(false, 0, subject);
    Map<String, Object> response = new HashMap<>();

    response.put("lastResponseUnixTime", System.currentTimeMillis());
    response.put("jobs", noteJobs);

    conn.send(serializeMessage(new Message(OP.LIST_NOTE_JOBS).put("noteJobs", response)));
  }

  public void broadcastUpdateNoteJobInfo(long lastUpdateUnixTime) throws IOException {
    List<Map<String, Object>> noteJobs = new LinkedList<>();
    Notebook notebookObject = notebook();
    List<Map<String, Object>> jobNotes = null;
    if (notebookObject != null) {
      jobNotes = notebook().getJobListByUnixTime(false, lastUpdateUnixTime, null);
      noteJobs = jobNotes == null ? noteJobs : jobNotes;
    }

    Map<String, Object> response = new HashMap<>();
    response.put("lastResponseUnixTime", System.currentTimeMillis());
    response.put("jobs", noteJobs != null ? noteJobs : new LinkedList<>());

    broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
        new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
  }

  public void unsubscribeNoteJobInfo(NotebookSocket conn) {
    removeConnectionFromNote(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(), conn);
  }

  public void saveInterpreterBindings(NotebookSocket conn, Message fromMessage) {
    String noteId = (String) fromMessage.data.get("noteId");
    try {
      List<String> settingIdList =
          gson.fromJson(String.valueOf(fromMessage.data.get("selectedSettingIds")),
              new TypeToken<ArrayList<String>>() {
              }.getType());
      AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
      notebook().bindInterpretersToNote(subject.getUser(), noteId, settingIdList);
      broadcastInterpreterBindings(noteId,
          InterpreterBindingUtils.getInterpreterBindings(notebook(), noteId));
    } catch (Exception e) {
      LOG.error("Error while saving interpreter bindings", e);
    }
  }

  public void getInterpreterBindings(NotebookSocket conn, Message fromMessage) throws IOException {
    String noteId = (String) fromMessage.data.get("noteId");
    List<InterpreterSettingsList> settingList =
        InterpreterBindingUtils.getInterpreterBindings(notebook(), noteId);
    conn.send(serializeMessage(
        new Message(OP.INTERPRETER_BINDINGS).put("interpreterBindings", settingList)));
  }

  public List<Map<String, String>> generateNotesInfo(boolean needsReload,
      AuthenticationInfo subject, Set<String> userAndRoles) {

    Notebook notebook = notebook();

    ZeppelinConfiguration conf = notebook.getConf();
    String homescreenNoteId = conf.getString(ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN);
    boolean hideHomeScreenNotebookFromList =
        conf.getBoolean(ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN_HIDE);

    if (needsReload) {
      try {
        notebook.reloadAllNotes(subject);
      } catch (IOException e) {
        LOG.error("Fail to reload notes from repository", e);
      }
    }

    List<Note> notes = notebook.getAllNotes(userAndRoles);
    List<Map<String, String>> notesInfo = new LinkedList<>();
    for (Note note : notes) {
      Map<String, String> info = new HashMap<>();

      if (hideHomeScreenNotebookFromList && note.getId().equals(homescreenNoteId)) {
        continue;
      }

      info.put("id", note.getId());
      info.put("name", note.getName());
      notesInfo.add(info);
    }

    return notesInfo;
  }

  public void broadcastNote(Note note) {
    broadcast(note.getId(), new Message(OP.NOTE).put("note", note));
  }

  public void broadcastInterpreterBindings(String noteId, List settingList) {
    broadcast(noteId, new Message(OP.INTERPRETER_BINDINGS).put("interpreterBindings", settingList));
  }

  public void broadcastParagraph(Note note, Paragraph p) {
    if (note.isPersonalizedMode()) {
      broadcastParagraphs(p.getUserParagraphMap(), p);
    } else {
      broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p));
    }
  }

  public void broadcastParagraphs(Map<String, Paragraph> userParagraphMap,
      Paragraph defaultParagraph) {
    if (null != userParagraphMap) {
      for (String user : userParagraphMap.keySet()) {
        multicastToUser(user,
            new Message(OP.PARAGRAPH).put("paragraph", userParagraphMap.get(user)));
      }
    }
  }

  private void broadcastNewParagraph(Note note, Paragraph para) {
    LOG.info("Broadcasting paragraph on run call instead of note.");
    int paraIndex = note.getParagraphs().indexOf(para);
    broadcast(note.getId(),
        new Message(OP.PARAGRAPH_ADDED).put("paragraph", para).put("index", paraIndex));
  }

  public void broadcastNoteList(AuthenticationInfo subject, HashSet userAndRoles) {
    if (subject == null) {
      subject = new AuthenticationInfo(StringUtils.EMPTY);
    }
    //send first to requesting user
    List<Map<String, String>> notesInfo = generateNotesInfo(false, subject, userAndRoles);
    multicastToUser(subject.getUser(), new Message(OP.NOTES_INFO).put("notes", notesInfo));
    //to others afterwards
    broadcastNoteListExcept(notesInfo, subject);
  }

  public void unicastNoteList(NotebookSocket conn, AuthenticationInfo subject,
      HashSet<String> userAndRoles) {
    List<Map<String, String>> notesInfo = generateNotesInfo(false, subject, userAndRoles);
    unicast(new Message(OP.NOTES_INFO).put("notes", notesInfo), conn);
  }

  public void broadcastReloadedNoteList(AuthenticationInfo subject, HashSet userAndRoles) {
    if (subject == null) {
      subject = new AuthenticationInfo(StringUtils.EMPTY);
    }

    //reload and reply first to requesting user
    List<Map<String, String>> notesInfo = generateNotesInfo(true, subject, userAndRoles);
    multicastToUser(subject.getUser(), new Message(OP.NOTES_INFO).put("notes", notesInfo));
    //to others afterwards
    broadcastNoteListExcept(notesInfo, subject);
  }

  private void broadcastNoteListExcept(List<Map<String, String>> notesInfo,
      AuthenticationInfo subject) {
    Set<String> userAndRoles;
    NotebookAuthorization authInfo = NotebookAuthorization.getInstance();
    for (String user : userConnectedSockets.keySet()) {
      if (subject.getUser().equals(user)) {
        continue;
      }
      //reloaded already above; parameter - false
      userAndRoles = authInfo.getRoles(user);
      userAndRoles.add(user);
      notesInfo = generateNotesInfo(false, new AuthenticationInfo(user), userAndRoles);
      multicastToUser(user, new Message(OP.NOTES_INFO).put("notes", notesInfo));
    }
  }

  void permissionError(NotebookSocket conn, String op, String userName, Set<String> userAndRoles,
      Set<String> allowed) throws IOException {
    LOG.info("Cannot {}. Connection readers {}. Allowed readers {}", op, userAndRoles, allowed);

    conn.send(serializeMessage(new Message(OP.AUTH_INFO).put("info",
        "Insufficient privileges to " + op + " note.\n\n" + "Allowed users or roles: " + allowed
            .toString() + "\n\n" + "But the user " + userName + " belongs to: " + userAndRoles
            .toString())));
  }

  /**
   * @return false if user doesn't have reader permission for this paragraph
   */
  private boolean hasParagraphReaderPermission(NotebookSocket conn,
                                              Notebook notebook, String noteId,
                                              HashSet<String> userAndRoles,
                                              String principal, String op)
      throws IOException {

    NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
    if (!notebookAuthorization.isReader(noteId, userAndRoles)) {
      permissionError(conn, op, principal, userAndRoles,
          notebookAuthorization.getOwners(noteId));
      return false;
    }

    return true;
  }

  /**
   * @return false if user doesn't have writer permission for this paragraph
   */
  private boolean hasParagraphWriterPermission(NotebookSocket conn,
                                               Notebook notebook, String noteId,
                                               HashSet<String> userAndRoles,
                                               String principal, String op)
      throws IOException {

    NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
    if (!notebookAuthorization.isWriter(noteId, userAndRoles)) {
      permissionError(conn, op, principal, userAndRoles,
          notebookAuthorization.getOwners(noteId));
      return false;
    }

    return true;
  }

  /**
   * @return false if user doesn't have owner permission for this paragraph
   */
  private boolean hasParagraphOwnerPermission(NotebookSocket conn,
                                              Notebook notebook, String noteId,
                                              HashSet<String> userAndRoles,
                                              String principal, String op)
      throws IOException {

    NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization();
    if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
      permissionError(conn, op, principal, userAndRoles,
          notebookAuthorization.getOwners(noteId));
      return false;
    }

    return true;
  }

  private void sendNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {

    LOG.info("New operation from {} : {} : {} : {} : {}", conn.getRequest().getRemoteAddr(),
        conn.getRequest().getRemotePort(), fromMessage.principal, fromMessage.op,
        fromMessage.get("id"));

    String noteId = (String) fromMessage.get("id");
    if (noteId == null) {
      return;
    }

    String user = fromMessage.principal;

    Note note = notebook.getNote(noteId);
    if (note != null) {

      if (!hasParagraphReaderPermission(conn, notebook, noteId,
          userAndRoles, fromMessage.principal, "read")) {
        return;
      }

      addConnectionToNote(note.getId(), conn);

      if (note.isPersonalizedMode()) {
        note = note.getUserNote(user);
      }
      conn.send(serializeMessage(new Message(OP.NOTE).put("note", note)));
      sendAllAngularObjects(note, user, conn);
    } else {
      conn.send(serializeMessage(new Message(OP.NOTE).put("note", null)));
    }
  }

  private void sendHomeNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    String noteId = notebook.getConf().getString(ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN);
    String user = fromMessage.principal;

    Note note = null;
    if (noteId != null) {
      note = notebook.getNote(noteId);
    }

    if (note != null) {
      if (!hasParagraphReaderPermission(conn, notebook, noteId,
          userAndRoles, fromMessage.principal, "read")) {
        return;
      }

      addConnectionToNote(note.getId(), conn);
      conn.send(serializeMessage(new Message(OP.NOTE).put("note", note)));
      sendAllAngularObjects(note, user, conn);
    } else {
      removeConnectionFromAllNote(conn);
      conn.send(serializeMessage(new Message(OP.NOTE).put("note", null)));
    }
  }

  private void updateNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws SchedulerException, IOException {
    String noteId = (String) fromMessage.get("id");
    String name = (String) fromMessage.get("name");
    Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
    if (noteId == null) {
      return;
    }
    if (config == null) {
      return;
    }

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "update")) {
      return;
    }

    Note note = notebook.getNote(noteId);
    if (note != null) {
      boolean cronUpdated = isCronUpdated(config, note.getConfig());
      note.setName(name);
      note.setConfig(config);
      if (cronUpdated) {
        notebook.refreshCron(note.getId());
      }

      AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
      note.persist(subject);
      broadcast(note.getId(), new Message(OP.NOTE_UPDATED).put("name", name).put("config", config)
          .put("info", note.getInfo()));
      broadcastNoteList(subject, userAndRoles);
    }
  }

  private void updatePersonalizedMode(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook, Message fromMessage) throws SchedulerException, IOException {
    String noteId = (String) fromMessage.get("id");
    String personalized = (String) fromMessage.get("personalized");
    boolean isPersonalized = personalized.equals("true") ? true : false;

    if (noteId == null) {
      return;
    }

    if (!hasParagraphOwnerPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "persoanlized")) {
      return;
    }

    Note note = notebook.getNote(noteId);
    if (note != null) {
      note.setPersonalizedMode(isPersonalized);
      AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
      note.persist(subject);
      broadcastNote(note);
    }
  }

  private void renameNote(NotebookSocket conn, HashSet<String> userAndRoles,
                          Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    renameNote(conn, userAndRoles, notebook, fromMessage, "rename");
  }

  private void renameNote(NotebookSocket conn, HashSet<String> userAndRoles,
                          Notebook notebook, Message fromMessage, String op)
      throws SchedulerException, IOException {
    String noteId = (String) fromMessage.get("id");
    String name = (String) fromMessage.get("name");

    if (noteId == null) {
      return;
    }

    if (!hasParagraphOwnerPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "rename")) {
      return;
    }

    Note note = notebook.getNote(noteId);
    if (note != null) {
      note.setName(name);

      AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
      note.persist(subject);
      broadcastNote(note);
      broadcastNoteList(subject, userAndRoles);
    }
  }

  private void renameFolder(NotebookSocket conn, HashSet<String> userAndRoles,
                            Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    renameFolder(conn, userAndRoles, notebook, fromMessage, "rename");
  }

  private void renameFolder(NotebookSocket conn, HashSet<String> userAndRoles,
                          Notebook notebook, Message fromMessage, String op)
      throws SchedulerException, IOException {
    String oldFolderId = (String) fromMessage.get("id");
    String newFolderId = (String) fromMessage.get("name");

    if (oldFolderId == null) {
      return;
    }

    for (Note note : notebook.getNotesUnderFolder(oldFolderId)) {
      String noteId = note.getId();
      if (!hasParagraphOwnerPermission(conn, notebook, noteId,
          userAndRoles, fromMessage.principal, op + " folder of '" + note.getName() + "'")) {
        return;
      }
    }

    Folder oldFolder = notebook.renameFolder(oldFolderId, newFolderId);

    if (oldFolder != null) {
      AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);

      List<Note> renamedNotes = oldFolder.getNotesRecursively();
      for (Note note : renamedNotes) {
        note.persist(subject);
        broadcastNote(note);
      }

      broadcastNoteList(subject, userAndRoles);
    }
  }

  private boolean isCronUpdated(Map<String, Object> configA, Map<String, Object> configB) {
    boolean cronUpdated = false;
    if (configA.get("cron") != null && configB.get("cron") != null && configA.get("cron")
        .equals(configB.get("cron"))) {
      cronUpdated = true;
    } else if (configA.get("cron") == null && configB.get("cron") == null) {
      cronUpdated = false;
    } else if (configA.get("cron") != null || configB.get("cron") != null) {
      cronUpdated = true;
    }

    return cronUpdated;
  }

  private void createNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message message) throws IOException {
    AuthenticationInfo subject = new AuthenticationInfo(message.principal);

    try {
      Note note = null;

      String defaultInterpreterId = (String) message.get("defaultInterpreterId");
      if (!StringUtils.isEmpty(defaultInterpreterId)) {
        List<String> interpreterSettingIds = new LinkedList<>();
        interpreterSettingIds.add(defaultInterpreterId);
        for (String interpreterSettingId : notebook.getInterpreterSettingManager().
            getDefaultInterpreterSettingList()) {
          if (!interpreterSettingId.equals(defaultInterpreterId)) {
            interpreterSettingIds.add(interpreterSettingId);
          }
        }
        note = notebook.createNote(interpreterSettingIds, subject);
      } else {
        note = notebook.createNote(subject);
      }

      note.addParagraph(subject); // it's an empty note. so add one paragraph
      if (message != null) {
        String noteName = (String) message.get("name");
        if (StringUtils.isEmpty(noteName)) {
          noteName = "Note " + note.getId();
        }
        note.setName(noteName);
      }

      note.persist(subject);
      addConnectionToNote(note.getId(), (NotebookSocket) conn);
      conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", note)));
    } catch (FileSystemException e) {
      LOG.error("Exception from createNote", e);
      conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
          "Oops! There is something wrong with the notebook file system. "
              + "Please check the logs for more details.")));
      return;
    }
    broadcastNoteList(subject, userAndRoles);
  }

  private void removeNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    String noteId = (String) fromMessage.get("id");
    if (noteId == null) {
      return;
    }

    if (!hasParagraphOwnerPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "remove")) {
      return;
    }

    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    notebook.removeNote(noteId, subject);
    removeNote(noteId);
    broadcastNoteList(subject, userAndRoles);
  }

  private void removeFolder(NotebookSocket conn, HashSet<String> userAndRoles,
                            Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    String folderId = (String) fromMessage.get("id");
    if (folderId == null) {
      return;
    }

    List<Note> notes = notebook.getNotesUnderFolder(folderId);
    for (Note note : notes) {
      String noteId = note.getId();

      if (!hasParagraphOwnerPermission(conn, notebook, noteId,
          userAndRoles, fromMessage.principal, "remove folder of '" + note.getName() + "'")) {
        return;
      }
    }

    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    for (Note note : notes) {
      notebook.removeNote(note.getId(), subject);
      removeNote(note.getId());
    }
    broadcastNoteList(subject, userAndRoles);
  }

  private void moveNoteToTrash(NotebookSocket conn, HashSet<String> userAndRoles,
                               Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    String noteId = (String) fromMessage.get("id");
    if (noteId == null) {
      return;
    }

    Note note = notebook.getNote(noteId);
    if (note != null && !note.isTrash()){
      fromMessage.put("name", Folder.TRASH_FOLDER_ID + "/" + note.getName());
      renameNote(conn, userAndRoles, notebook, fromMessage, "move");
    }
  }

  private void moveFolderToTrash(NotebookSocket conn, HashSet<String> userAndRoles,
                                 Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    String folderId = (String) fromMessage.get("id");
    if (folderId == null) {
      return;
    }

    Folder folder = notebook.getFolder(folderId);
    if (folder != null && !folder.isTrash()) {
      String trashFolderId = Folder.TRASH_FOLDER_ID + "/" + folderId;
      if (notebook.hasFolder(trashFolderId)){
        DateTime currentDate = new DateTime();
        DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
        trashFolderId += Folder.TRASH_FOLDER_CONFLICT_INFIX + formatter.print(currentDate);
      }

      fromMessage.put("name", trashFolderId);
      renameFolder(conn, userAndRoles, notebook, fromMessage, "move");
    }
  }

  private void restoreNote(NotebookSocket conn, HashSet<String> userAndRoles,
                          Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    String noteId = (String) fromMessage.get("id");

    if (noteId == null) {
      return;
    }

    Note note = notebook.getNote(noteId);
    if (note != null && note.isTrash()) {
      fromMessage.put("name", note.getName().replaceFirst(Folder.TRASH_FOLDER_ID + "/", ""));
      renameNote(conn, userAndRoles, notebook, fromMessage, "restore");
    }
  }

  private void restoreFolder(NotebookSocket conn, HashSet<String> userAndRoles,
                            Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    String folderId = (String) fromMessage.get("id");

    if (folderId == null) {
      return;
    }

    Folder folder = notebook.getFolder(folderId);
    if (folder != null && folder.isTrash()) {
      String restoreName = folder.getId().replaceFirst(Folder.TRASH_FOLDER_ID + "/", "").trim();

      // if the folder had conflict when it had moved to trash before
      Pattern p = Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$");
      Matcher m = p.matcher(restoreName);
      restoreName = m.replaceAll("").trim();

      fromMessage.put("name", restoreName);
      renameFolder(conn, userAndRoles, notebook, fromMessage, "restore");
    }
  }

  private void restoreAll(NotebookSocket conn, HashSet<String> userAndRoles,
                             Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    Folder trashFolder = notebook.getFolder(Folder.TRASH_FOLDER_ID);
    if (trashFolder != null) {
      fromMessage.data = new HashMap<>();
      fromMessage.put("id", Folder.TRASH_FOLDER_ID);
      fromMessage.put("name", Folder.ROOT_FOLDER_ID);
      renameFolder(conn, userAndRoles, notebook, fromMessage, "restore trash");
    }
  }

  private void emptyTrash(NotebookSocket conn, HashSet<String> userAndRoles,
                          Notebook notebook, Message fromMessage)
      throws SchedulerException, IOException {
    fromMessage.data = new HashMap<>();
    fromMessage.put("id", Folder.TRASH_FOLDER_ID);
    removeFolder(conn, userAndRoles, notebook, fromMessage);
  }

  private void updateParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
                               Notebook notebook, Message fromMessage) throws IOException {
    String paragraphId = (String) fromMessage.get("id");
    if (paragraphId == null) {
      return;
    }

    Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
    Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
    String noteId = getOpenNoteId(conn);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return;
    }

    final Note note = notebook.getNote(noteId);
    Paragraph p = note.getParagraph(paragraphId);

    p.settings.setParams(params);
    p.setConfig(config);
    p.setTitle((String) fromMessage.get("title"));
    p.setText((String) fromMessage.get("paragraph"));

    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    if (note.isPersonalizedMode()) {
      p = p.getUserParagraph(subject.getUser());
      p.settings.setParams(params);
      p.setConfig(config);
      p.setTitle((String) fromMessage.get("title"));
      p.setText((String) fromMessage.get("paragraph"));
    }

    note.persist(subject);

    if (note.isPersonalizedMode()) {
      Map<String, Paragraph> userParagraphMap =
          note.getParagraph(paragraphId).getUserParagraphMap();
      broadcastParagraphs(userParagraphMap, p);
    } else {
      broadcastParagraph(note, p);
    }
  }

  private void cloneNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException, CloneNotSupportedException {
    String noteId = getOpenNoteId(conn);
    String name = (String) fromMessage.get("name");
    Note newNote = notebook.cloneNote(noteId, name, new AuthenticationInfo(fromMessage.principal));
    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    addConnectionToNote(newNote.getId(), (NotebookSocket) conn);
    conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", newNote)));
    broadcastNoteList(subject, userAndRoles);
  }

  private void clearAllParagraphOutput(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook, Message fromMessage) throws IOException {
    final String noteId = (String) fromMessage.get("id");
    if (StringUtils.isBlank(noteId)) {
      return;
    }

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "clear output")) {
      return;
    }

    Note note = notebook.getNote(noteId);
    note.clearAllParagraphOutput();
    broadcastNote(note);
  }

  protected Note importNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    Note note = null;
    if (fromMessage != null) {
      String noteName = (String) ((Map) fromMessage.get("note")).get("name");
      String noteJson = gson.toJson(fromMessage.get("note"));
      AuthenticationInfo subject = null;
      if (fromMessage.principal != null) {
        subject = new AuthenticationInfo(fromMessage.principal);
      } else {
        subject = new AuthenticationInfo("anonymous");
      }
      note = notebook.importNote(noteJson, noteName, subject);
      note.persist(subject);
      broadcastNote(note);
      broadcastNoteList(subject, userAndRoles);
    }
    return note;
  }

  private void removeParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    final String paragraphId = (String) fromMessage.get("id");
    if (paragraphId == null) {
      return;
    }
    String noteId = getOpenNoteId(conn);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return;
    }

    /** We dont want to remove the last paragraph */
    final Note note = notebook.getNote(noteId);
    if (!note.isLastParagraph(paragraphId)) {
      AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
      Paragraph para = note.removeParagraph(subject.getUser(), paragraphId);
      note.persist(subject);
      if (para != null) {
        broadcast(note.getId(), new Message(OP.PARAGRAPH_REMOVED).
            put("id", para.getId()));
      }
    }
  }

  private void clearParagraphOutput(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook, Message fromMessage) throws IOException {
    final String paragraphId = (String) fromMessage.get("id");
    if (paragraphId == null) {
      return;
    }

    String noteId = getOpenNoteId(conn);
    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return;
    }

    final Note note = notebook.getNote(noteId);
    note.clearParagraphOutput(paragraphId);
    Paragraph paragraph = note.getParagraph(paragraphId);
    broadcastParagraph(note, paragraph);
  }

  private void completion(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    String paragraphId = (String) fromMessage.get("id");
    String buffer = (String) fromMessage.get("buf");
    int cursor = (int) Double.parseDouble(fromMessage.get("cursor").toString());
    Message resp = new Message(OP.COMPLETION_LIST).put("id", paragraphId);
    if (paragraphId == null) {
      conn.send(serializeMessage(resp));
      return;
    }

    final Note note = notebook.getNote(getOpenNoteId(conn));
    List<InterpreterCompletion> candidates = note.completion(paragraphId, buffer, cursor);
    resp.put("completions", candidates);
    conn.send(serializeMessage(resp));
  }

  /**
   * When angular object updated from client
   *
   * @param conn the web socket.
   * @param notebook the notebook.
   * @param fromMessage the message.
   */
  private void angularObjectUpdated(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook, Message fromMessage) {
    String noteId = (String) fromMessage.get("noteId");
    String paragraphId = (String) fromMessage.get("paragraphId");
    String interpreterGroupId = (String) fromMessage.get("interpreterGroupId");
    String varName = (String) fromMessage.get("name");
    Object varValue = fromMessage.get("value");
    String user = fromMessage.principal;
    AngularObject ao = null;
    boolean global = false;
    // propagate change to (Remote) AngularObjectRegistry
    Note note = notebook.getNote(noteId);
    if (note != null) {
      List<InterpreterSetting> settings =
          notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId());
      for (InterpreterSetting setting : settings) {
        if (setting.getInterpreterGroup(user, note.getId()) == null) {
          continue;
        }
        if (interpreterGroupId.equals(setting.getInterpreterGroup(user, note.getId()).getId())) {
          AngularObjectRegistry angularObjectRegistry =
              setting.getInterpreterGroup(user, note.getId()).getAngularObjectRegistry();

          // first trying to get local registry
          ao = angularObjectRegistry.get(varName, noteId, paragraphId);
          if (ao == null) {
            // then try notebook scope registry
            ao = angularObjectRegistry.get(varName, noteId, null);
            if (ao == null) {
              // then try global scope registry
              ao = angularObjectRegistry.get(varName, null, null);
              if (ao == null) {
                LOG.warn("Object {} is not binded", varName);
              } else {
                // path from client -> server
                ao.set(varValue, false);
                global = true;
              }
            } else {
              // path from client -> server
              ao.set(varValue, false);
              global = false;
            }
          } else {
            ao.set(varValue, false);
            global = false;
          }
          break;
        }
      }
    }

    if (global) { // broadcast change to all web session that uses related
      // interpreter.
      for (Note n : notebook.getAllNotes()) {
        List<InterpreterSetting> settings =
            notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId());
        for (InterpreterSetting setting : settings) {
          if (setting.getInterpreterGroup(user, n.getId()) == null) {
            continue;
          }
          if (interpreterGroupId.equals(setting.getInterpreterGroup(user, n.getId()).getId())) {
            AngularObjectRegistry angularObjectRegistry =
                setting.getInterpreterGroup(user, n.getId()).getAngularObjectRegistry();
            this.broadcastExcept(n.getId(),
                new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", ao)
                    .put("interpreterGroupId", interpreterGroupId).put("noteId", n.getId())
                    .put("paragraphId", ao.getParagraphId()), conn);
          }
        }
      }
    } else { // broadcast to all web session for the note
      this.broadcastExcept(note.getId(),
          new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", ao)
              .put("interpreterGroupId", interpreterGroupId).put("noteId", note.getId())
              .put("paragraphId", ao.getParagraphId()), conn);
    }
  }

  /**
   * Push the given Angular variable to the target
   * interpreter angular registry given a noteId
   * and a paragraph id
   */
  protected void angularObjectClientBind(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook, Message fromMessage) throws Exception {
    String noteId = fromMessage.getType("noteId");
    String varName = fromMessage.getType("name");
    Object varValue = fromMessage.get("value");
    String paragraphId = fromMessage.getType("paragraphId");
    Note note = notebook.getNote(noteId);

    if (paragraphId == null) {
      throw new IllegalArgumentException(
          "target paragraph not specified for " + "angular value bind");
    }

    if (note != null) {
      final InterpreterGroup interpreterGroup = findInterpreterGroupForParagraph(note, paragraphId);

      final AngularObjectRegistry registry = interpreterGroup.getAngularObjectRegistry();
      if (registry instanceof RemoteAngularObjectRegistry) {

        RemoteAngularObjectRegistry remoteRegistry = (RemoteAngularObjectRegistry) registry;
        pushAngularObjectToRemoteRegistry(noteId, paragraphId, varName, varValue, remoteRegistry,
            interpreterGroup.getId(), conn);

      } else {
        pushAngularObjectToLocalRepo(noteId, paragraphId, varName, varValue, registry,
            interpreterGroup.getId(), conn);
      }
    }
  }

  /**
   * Remove the given Angular variable to the target
   * interpreter(s) angular registry given a noteId
   * and an optional list of paragraph id(s)
   */
  protected void angularObjectClientUnbind(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook, Message fromMessage) throws Exception {
    String noteId = fromMessage.getType("noteId");
    String varName = fromMessage.getType("name");
    String paragraphId = fromMessage.getType("paragraphId");
    Note note = notebook.getNote(noteId);

    if (paragraphId == null) {
      throw new IllegalArgumentException(
          "target paragraph not specified for " + "angular value unBind");
    }

    if (note != null) {
      final InterpreterGroup interpreterGroup = findInterpreterGroupForParagraph(note, paragraphId);

      final AngularObjectRegistry registry = interpreterGroup.getAngularObjectRegistry();

      if (registry instanceof RemoteAngularObjectRegistry) {
        RemoteAngularObjectRegistry remoteRegistry = (RemoteAngularObjectRegistry) registry;
        removeAngularFromRemoteRegistry(noteId, paragraphId, varName, remoteRegistry,
            interpreterGroup.getId(), conn);
      } else {
        removeAngularObjectFromLocalRepo(noteId, paragraphId, varName, registry,
            interpreterGroup.getId(), conn);
      }
    }
  }

  private InterpreterGroup findInterpreterGroupForParagraph(Note note, String paragraphId)
      throws Exception {
    final Paragraph paragraph = note.getParagraph(paragraphId);
    if (paragraph == null) {
      throw new IllegalArgumentException("Unknown paragraph with id : " + paragraphId);
    }
    return paragraph.getCurrentRepl().getInterpreterGroup();
  }

  private void pushAngularObjectToRemoteRegistry(String noteId, String paragraphId, String varName,
      Object varValue, RemoteAngularObjectRegistry remoteRegistry, String interpreterGroupId,
      NotebookSocket conn) {

    final AngularObject ao =
        remoteRegistry.addAndNotifyRemoteProcess(varName, varValue, noteId, paragraphId);

    this.broadcastExcept(noteId, new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", ao)
        .put("interpreterGroupId", interpreterGroupId).put("noteId", noteId)
        .put("paragraphId", paragraphId), conn);
  }

  private void removeAngularFromRemoteRegistry(String noteId, String paragraphId, String varName,
      RemoteAngularObjectRegistry remoteRegistry, String interpreterGroupId, NotebookSocket conn) {
    final AngularObject ao =
        remoteRegistry.removeAndNotifyRemoteProcess(varName, noteId, paragraphId);
    this.broadcastExcept(noteId, new Message(OP.ANGULAR_OBJECT_REMOVE).put("angularObject", ao)
        .put("interpreterGroupId", interpreterGroupId).put("noteId", noteId)
        .put("paragraphId", paragraphId), conn);
  }

  private void pushAngularObjectToLocalRepo(String noteId, String paragraphId, String varName,
      Object varValue, AngularObjectRegistry registry, String interpreterGroupId,
      NotebookSocket conn) {
    AngularObject angularObject = registry.get(varName, noteId, paragraphId);
    if (angularObject == null) {
      angularObject = registry.add(varName, varValue, noteId, paragraphId);
    } else {
      angularObject.set(varValue, true);
    }

    this.broadcastExcept(noteId,
        new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", angularObject)
            .put("interpreterGroupId", interpreterGroupId).put("noteId", noteId)
            .put("paragraphId", paragraphId), conn);
  }

  private void removeAngularObjectFromLocalRepo(String noteId, String paragraphId, String varName,
      AngularObjectRegistry registry, String interpreterGroupId, NotebookSocket conn) {
    final AngularObject removed = registry.remove(varName, noteId, paragraphId);
    if (removed != null) {
      this.broadcastExcept(noteId,
          new Message(OP.ANGULAR_OBJECT_REMOVE).put("angularObject", removed)
              .put("interpreterGroupId", interpreterGroupId).put("noteId", noteId)
              .put("paragraphId", paragraphId), conn);
    }
  }

  private void moveParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    final String paragraphId = (String) fromMessage.get("id");
    if (paragraphId == null) {
      return;
    }

    final int newIndex = (int) Double.parseDouble(fromMessage.get("index").toString());
    String noteId = getOpenNoteId(conn);
    final Note note = notebook.getNote(noteId);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return;
    }

    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    note.moveParagraph(paragraphId, newIndex);
    note.persist(subject);
    broadcast(note.getId(),
        new Message(OP.PARAGRAPH_MOVED).put("id", paragraphId).put("index", newIndex));
  }

  private String insertParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook, Message fromMessage) throws IOException {
    final int index = (int) Double.parseDouble(fromMessage.get("index").toString());
    String noteId = getOpenNoteId(conn);
    final Note note = notebook.getNote(noteId);
    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return null;
    }

    Paragraph newPara = note.insertParagraph(index, subject);
    note.persist(subject);
    broadcastNewParagraph(note, newPara);

    return newPara.getId();
  }

  private void copyParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    String newParaId = insertParagraph(conn, userAndRoles, notebook, fromMessage);

    if (newParaId == null) {
      return;
    }
    fromMessage.put("id", newParaId);

    updateParagraph(conn, userAndRoles, notebook, fromMessage);
  }

  private void cancelParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {
    final String paragraphId = (String) fromMessage.get("id");
    if (paragraphId == null) {
      return;
    }

    String noteId = getOpenNoteId(conn);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return;
    }

    final Note note = notebook.getNote(noteId);
    Paragraph p = note.getParagraph(paragraphId);
    p.abort();
  }

  private void runAllParagraphs(NotebookSocket conn, HashSet<String> userAndRoles,
                                Notebook notebook,
      Message fromMessage) throws IOException {
    final String noteId = (String) fromMessage.get("noteId");
    if (StringUtils.isBlank(noteId)) {
      return;
    }

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "run all paragraphs")) {
      return;
    }

    List<Map<String, Object>> paragraphs =
        gson.fromJson(String.valueOf(fromMessage.data.get("paragraphs")),
            new TypeToken<List<Map<String, Object>>>() {}.getType());

    for (Map<String, Object> raw : paragraphs) {
      String paragraphId = (String) raw.get("id");
      if (paragraphId == null) {
        continue;
      }

      String text = (String) raw.get("paragraph");
      String title = (String) raw.get("title");
      Map<String, Object> params = (Map<String, Object>) raw.get("params");
      Map<String, Object> config = (Map<String, Object>) raw.get("config");

      Note note = notebook.getNote(noteId);
      Paragraph p = setParagraphUsingMessage(note, fromMessage,
          paragraphId, text, title, params, config);

      persistAndExecuteSingleParagraph(conn, note, p);
    }
  }

  private void broadcastSpellExecution(NotebookSocket conn, HashSet<String> userAndRoles,
                                       Notebook notebook, Message fromMessage)
      throws IOException {

    final String paragraphId = (String) fromMessage.get("id");
    if (paragraphId == null) {
      return;
    }

    String noteId = getOpenNoteId(conn);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return;
    }

    String text = (String) fromMessage.get("paragraph");
    String title = (String) fromMessage.get("title");
    Status status = Status.valueOf((String) fromMessage.get("status"));
    Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
    Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");

    final Note note = notebook.getNote(noteId);
    Paragraph p = setParagraphUsingMessage(note, fromMessage, paragraphId,
        text, title, params, config);
    p.setResult(fromMessage.get("results"));
    p.setErrorMessage((String) fromMessage.get("errorMessage"));
    p.setStatusWithoutNotification(status);

    addNewParagraphIfLastParagraphIsExecuted(note, p);
    if (!persistNoteWithAuthInfo(conn, note, p)) {
      return;
    }

    // broadcast to other clients only
    broadcastExcept(note.getId(),
        new Message(OP.RUN_PARAGRAPH_USING_SPELL).put("paragraph", p), conn);
  }

  private void runParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
                            Message fromMessage) throws IOException {
    final String paragraphId = (String) fromMessage.get("id");
    if (paragraphId == null) {
      return;
    }

    String noteId = getOpenNoteId(conn);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "write")) {
      return;
    }

    String text = (String) fromMessage.get("paragraph");
    String title = (String) fromMessage.get("title");
    Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
    Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");

    final Note note = notebook.getNote(noteId);
    Paragraph p = setParagraphUsingMessage(note, fromMessage, paragraphId,
        text, title, params, config);

    persistAndExecuteSingleParagraph(conn, note, p);
  }

  private void addNewParagraphIfLastParagraphIsExecuted(Note note, Paragraph p) {
    // if it's the last paragraph and empty, let's add a new one
    boolean isTheLastParagraph = note.isLastParagraph(p.getId());
    if (!(p.getText().trim().equals(p.getMagic()) ||
        Strings.isNullOrEmpty(p.getText())) &&
        isTheLastParagraph) {
      Paragraph newPara = note.addParagraph(p.getAuthenticationInfo());
      broadcastNewParagraph(note, newPara);
    }
  }

  /**
   * @return false if failed to save a note
   */
  private boolean persistNoteWithAuthInfo(NotebookSocket conn,
                                          Note note, Paragraph p) throws IOException {
    try {
      note.persist(p.getAuthenticationInfo());
      return true;
    } catch (FileSystemException ex) {
      LOG.error("Exception from run", ex);
      conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
          "Oops! There is something wrong with the notebook file system. "
              + "Please check the logs for more details.")));
      // don't run the paragraph when there is error on persisting the note information
      return false;
    }
  }

  private void persistAndExecuteSingleParagraph(NotebookSocket conn,
                                                Note note, Paragraph p) throws IOException {
    addNewParagraphIfLastParagraphIsExecuted(note, p);
    if (!persistNoteWithAuthInfo(conn, note, p)) {
      return;
    }

    try {
      note.run(p.getId());
    } catch (Exception ex) {
      LOG.error("Exception from run", ex);
      if (p != null) {
        p.setReturn(new InterpreterResult(InterpreterResult.Code.ERROR, ex.getMessage()), ex);
        p.setStatus(Status.ERROR);
        broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p));
      }
    }
  }

  private Paragraph setParagraphUsingMessage(Note note, Message fromMessage, String paragraphId,
                                             String text, String title, Map<String, Object> params,
                                             Map<String, Object> config) {
    Paragraph p = note.getParagraph(paragraphId);
    p.setText(text);
    p.setTitle(title);
    AuthenticationInfo subject =
        new AuthenticationInfo(fromMessage.principal, fromMessage.ticket);
    p.setAuthenticationInfo(subject);
    p.settings.setParams(params);
    p.setConfig(config);

    if (note.isPersonalizedMode()) {
      p = note.getParagraph(paragraphId);
      p.setText(text);
      p.setTitle(title);
      p.setAuthenticationInfo(subject);
      p.settings.setParams(params);
      p.setConfig(config);
    }

    return p;
  }

  private void sendAllConfigurations(NotebookSocket conn, HashSet<String> userAndRoles,
      Notebook notebook) throws IOException {
    ZeppelinConfiguration conf = notebook.getConf();

    Map<String, String> configurations =
        conf.dumpConfigurations(conf, new ZeppelinConfiguration.ConfigurationKeyPredicate() {
          @Override
          public boolean apply(String key) {
            return !key.contains("password") && !key.equals(
                ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_AZURE_CONNECTION_STRING
                    .getVarName());
          }
        });

    conn.send(serializeMessage(
        new Message(OP.CONFIGURATIONS_INFO).put("configurations", configurations)));
  }

  private void checkpointNote(NotebookSocket conn, Notebook notebook, Message fromMessage)
      throws IOException {
    String noteId = (String) fromMessage.get("noteId");
    String commitMessage = (String) fromMessage.get("commitMessage");
    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    Revision revision = notebook.checkpointNote(noteId, commitMessage, subject);
    if (!Revision.isEmpty(revision)) {
      List<Revision> revisions = notebook.listRevisionHistory(noteId, subject);
      conn.send(
          serializeMessage(new Message(OP.LIST_REVISION_HISTORY).put("revisionList", revisions)));
    } else {
      conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
          "Couldn't checkpoint note revision: possibly storage doesn't support versioning. "
              + "Please check the logs for more details.")));
    }
  }

  private void listRevisionHistory(NotebookSocket conn, Notebook notebook, Message fromMessage)
      throws IOException {
    String noteId = (String) fromMessage.get("noteId");
    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    List<Revision> revisions = notebook.listRevisionHistory(noteId, subject);

    conn.send(
        serializeMessage(new Message(OP.LIST_REVISION_HISTORY).put("revisionList", revisions)));
  }

  private void setNoteRevision(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
      Message fromMessage) throws IOException {

    String noteId = (String) fromMessage.get("noteId");
    String revisionId = (String) fromMessage.get("revisionId");
    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);

    if (!hasParagraphWriterPermission(conn, notebook, noteId,
        userAndRoles, fromMessage.principal, "update")) {
      return;
    }

    Note headNote = null;
    boolean setRevisionStatus;
    try {
      headNote = notebook.setNoteRevision(noteId, revisionId, subject);
      setRevisionStatus = headNote != null;
    } catch (Exception e) {
      setRevisionStatus = false;
      LOG.error("Failed to set given note revision", e);
    }
    if (setRevisionStatus) {
      notebook.loadNoteFromRepo(noteId, subject);
    }

    conn.send(serializeMessage(new Message(OP.SET_NOTE_REVISION).put("status", setRevisionStatus)));

    if (setRevisionStatus) {
      Note reloadedNote = notebook.getNote(headNote.getId());
      broadcastNote(reloadedNote);
    } else {
      conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
          "Couldn't set note to the given revision. "
              + "Please check the logs for more details.")));
    }
  }

  private void getNoteByRevision(NotebookSocket conn, Notebook notebook, Message fromMessage)
      throws IOException {
    String noteId = (String) fromMessage.get("noteId");
    String revisionId = (String) fromMessage.get("revisionId");
    AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
    Note revisionNote = notebook.getNoteByRevision(noteId, revisionId, subject);
    conn.send(serializeMessage(
        new Message(OP.NOTE_REVISION).put("noteId", noteId).put("revisionId", revisionId)
            .put("note", revisionNote)));
  }

  /**
   * This callback is for the paragraph that runs on ZeppelinServer
   *
   * @param output output to append
   */
  @Override
  public void onOutputAppend(String noteId, String paragraphId, int index, String output) {
    Message msg = new Message(OP.PARAGRAPH_APPEND_OUTPUT).put("noteId", noteId)
        .put("paragraphId", paragraphId).put("index", index).put("data", output);
    broadcast(noteId, msg);
  }

  /**
   * This callback is for the paragraph that runs on ZeppelinServer
   *
   * @param output output to update (replace)
   */
  @Override
  public void onOutputUpdated(String noteId, String paragraphId, int index,
      InterpreterResult.Type type, String output) {
    Message msg = new Message(OP.PARAGRAPH_UPDATE_OUTPUT).put("noteId", noteId)
        .put("paragraphId", paragraphId).put("index", index).put("type", type).put("data", output);
    Note note = notebook().getNote(noteId);
    if (note.isPersonalizedMode()) {
      String user = note.getParagraph(paragraphId).getUser();
      if (null != user) {
        multicastToUser(user, msg);
      }
    } else {
      broadcast(noteId, msg);
    }
  }

  /**
   * This callback is for the paragraph that runs on ZeppelinServer
   */
  @Override
  public void onOutputClear(String noteId, String paragraphId) {
    Notebook notebook = notebook();
    final Note note = notebook.getNote(noteId);
    note.clearParagraphOutput(paragraphId);
    Paragraph paragraph = note.getParagraph(paragraphId);
    broadcastParagraph(note, paragraph);
  }

  /**
   * When application append output
   */
  @Override
  public void onOutputAppend(String noteId, String paragraphId, int index, String appId,
      String output) {
    Message msg =
        new Message(OP.APP_APPEND_OUTPUT).put("noteId", noteId).put("paragraphId", paragraphId)
            .put("index", index).put("appId", appId).put("data", output);
    broadcast(noteId, msg);
  }

  /**
   * When application update output
   */
  @Override
  public void onOutputUpdated(String noteId, String paragraphId, int index, String appId,
      InterpreterResult.Type type, String output) {
    Message msg =
        new Message(OP.APP_UPDATE_OUTPUT).put("noteId", noteId).put("paragraphId", paragraphId)
            .put("index", index).put("type", type).put("appId", appId).put("data", output);
    broadcast(noteId, msg);
  }

  @Override
  public void onLoad(String noteId, String paragraphId, String appId, HeliumPackage pkg) {
    Message msg = new Message(OP.APP_LOAD).put("noteId", noteId).put("paragraphId", paragraphId)
        .put("appId", appId).put("pkg", pkg);
    broadcast(noteId, msg);
  }

  @Override
  public void onStatusChange(String noteId, String paragraphId, String appId, String status) {
    Message msg =
        new Message(OP.APP_STATUS_CHANGE).put("noteId", noteId).put("paragraphId", paragraphId)
            .put("appId", appId).put("status", status);
    broadcast(noteId, msg);
  }

  @Override
  public void onGetParagraphRunners(String noteId, String paragraphId,
      RemoteWorksEventListener callback) {
    Notebook notebookIns = notebook();
    List<InterpreterContextRunner> runner = new LinkedList<>();

    if (notebookIns == null) {
      LOG.info("intepreter request notebook instance is null");
      callback.onFinished(notebookIns);
    }

    try {
      Note note = notebookIns.getNote(noteId);
      if (note != null) {
        if (paragraphId != null) {
          Paragraph paragraph = note.getParagraph(paragraphId);
          if (paragraph != null) {
            runner.add(paragraph.getInterpreterContextRunner());
          }
        } else {
          for (Paragraph p : note.getParagraphs()) {
            runner.add(p.getInterpreterContextRunner());
          }
        }
      }
      callback.onFinished(runner);
    } catch (NullPointerException e) {
      LOG.warn(e.getMessage());
      callback.onError();
    }
  }

  @Override
  public void onRemoteRunParagraph(String noteId, String paragraphId) throws Exception {
    Notebook notebookIns = notebook();
    try {
      if (notebookIns == null) {
        throw new Exception("onRemoteRunParagraph notebook instance is null");
      }
      Note noteIns = notebookIns.getNote(noteId);
      if (noteIns == null) {
        throw new Exception(String.format("Can't found note id %s", noteId));
      }

      Paragraph paragraph = noteIns.getParagraph(paragraphId);
      if (paragraph == null) {
        throw new Exception(String.format("Can't found paragraph %s %s", noteId, paragraphId));
      }

      Set<String> userAndRoles = Sets.newHashSet();
      userAndRoles.add(SecurityUtils.getPrincipal());
      userAndRoles.addAll(SecurityUtils.getRoles());
      if (!notebookIns.getNotebookAuthorization().hasWriteAuthorization(userAndRoles, noteId)) {
        throw new ForbiddenException(String.format("can't execute note %s", noteId));
      }

      AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
      paragraph.setAuthenticationInfo(subject);

      noteIns.run(paragraphId);

    } catch (Exception e) {
      throw e;
    }
  }

  /**
   * Notebook Information Change event
   */
  public static class NotebookInformationListener implements NotebookEventListener {

    private NotebookServer notebookServer;

    public NotebookInformationListener(NotebookServer notebookServer) {
      this.notebookServer = notebookServer;
    }

    @Override
    public void onParagraphRemove(Paragraph p) {
      try {
        notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
      } catch (IOException ioe) {
        LOG.error("can not broadcast for job manager {}", ioe.getMessage());
      }
    }

    @Override
    public void onNoteRemove(Note note) {
      try {
        notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
      } catch (IOException ioe) {
        LOG.error("can not broadcast for job manager {}", ioe.getMessage());
      }

      List<Map<String, Object>> notesInfo = new LinkedList<>();
      Map<String, Object> info = new HashMap<>();
      info.put("noteId", note.getId());
      // set paragraphs
      List<Map<String, Object>> paragraphsInfo = new LinkedList<>();

      // notebook json object root information.
      info.put("isRunningJob", false);
      info.put("unixTimeLastRun", 0);
      info.put("isRemoved", true);
      info.put("paragraphs", paragraphsInfo);
      notesInfo.add(info);

      Map<String, Object> response = new HashMap<>();
      response.put("lastResponseUnixTime", System.currentTimeMillis());
      response.put("jobs", notesInfo);

      notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
          new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));

    }

    @Override
    public void onParagraphCreate(Paragraph p) {
      Notebook notebook = notebookServer.notebook();
      List<Map<String, Object>> notebookJobs = notebook.getJobListByParagraphId(p.getId());
      Map<String, Object> response = new HashMap<>();
      response.put("lastResponseUnixTime", System.currentTimeMillis());
      response.put("jobs", notebookJobs);

      notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
          new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
    }

    @Override
    public void onNoteCreate(Note note) {
      Notebook notebook = notebookServer.notebook();
      List<Map<String, Object>> notebookJobs = notebook.getJobListByNoteId(note.getId());
      Map<String, Object> response = new HashMap<>();
      response.put("lastResponseUnixTime", System.currentTimeMillis());
      response.put("jobs", notebookJobs);

      notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
          new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
    }

    @Override
    public void onParagraphStatusChange(Paragraph p, Status status) {
      Notebook notebook = notebookServer.notebook();
      List<Map<String, Object>> notebookJobs = notebook.getJobListByParagraphId(p.getId());

      Map<String, Object> response = new HashMap<>();
      response.put("lastResponseUnixTime", System.currentTimeMillis());
      response.put("jobs", notebookJobs);

      notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
          new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
    }

    @Override
    public void onUnbindInterpreter(Note note, InterpreterSetting setting) {
      Notebook notebook = notebookServer.notebook();
      List<Map<String, Object>> notebookJobs = notebook.getJobListByNoteId(note.getId());
      Map<String, Object> response = new HashMap<>();
      response.put("lastResponseUnixTime", System.currentTimeMillis());
      response.put("jobs", notebookJobs);

      notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
          new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
    }
  }

  /**
   * Need description here.
   */
  public static class ParagraphListenerImpl implements ParagraphJobListener {

    private NotebookServer notebookServer;
    private Note note;

    public ParagraphListenerImpl(NotebookServer notebookServer, Note note) {
      this.notebookServer = notebookServer;
      this.note = note;
    }

    @Override
    public void onProgressUpdate(Job job, int progress) {
      notebookServer.broadcast(note.getId(),
          new Message(OP.PROGRESS).put("id", job.getId()).put("progress", job.progress()));
    }

    @Override
    public void beforeStatusChange(Job job, Status before, Status after) {
    }

    @Override
    public void afterStatusChange(Job job, Status before, Status after) {
      if (after == Status.ERROR) {
        if (job.getException() != null) {
          LOG.error("Error", job.getException());
        }
      }

      if (job.isTerminated()) {
        if (job.getStatus() == Status.FINISHED) {
          LOG.info("Job {} is finished successfully, status: {}", job.getId(), job.getStatus());
        } else {
          LOG.warn("Job {} is finished, status: {}, exception: {}, result: {}" , job.getId(),
              job.getStatus(), job.getException(), job.getReturn());
        }

        try {
          //TODO(khalid): may change interface for JobListener and pass subject from interpreter
          note.persist(job instanceof Paragraph ? ((Paragraph) job).getAuthenticationInfo() : null);
        } catch (IOException e) {
          LOG.error(e.toString(), e);
        }
      }
      if (job instanceof Paragraph) {
        Paragraph p = (Paragraph) job;
        p.setStatusToUserParagraph(job.getStatus());
        notebookServer.broadcastParagraph(note, p);
      }
      try {
        notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
      } catch (IOException e) {
        LOG.error("can not broadcast for job manager {}", e);
      }
    }

    /**
     * This callback is for paragraph that runs on RemoteInterpreterProcess
     */
    @Override
    public void onOutputAppend(Paragraph paragraph, int idx, String output) {
      Message msg =
          new Message(OP.PARAGRAPH_APPEND_OUTPUT).put("noteId", paragraph.getNote().getId())
              .put("paragraphId", paragraph.getId()).put("data", output);

      notebookServer.broadcast(paragraph.getNote().getId(), msg);
    }

    /**
     * This callback is for paragraph that runs on RemoteInterpreterProcess
     */
    @Override
    public void onOutputUpdate(Paragraph paragraph, int idx, InterpreterResultMessage result) {
      String output = result.getData();
      Message msg =
          new Message(OP.PARAGRAPH_UPDATE_OUTPUT).put("noteId", paragraph.getNote().getId())
              .put("paragraphId", paragraph.getId()).put("data", output);

      notebookServer.broadcast(paragraph.getNote().getId(), msg);
    }

    @Override
    public void onOutputUpdateAll(Paragraph paragraph, List<InterpreterResultMessage> msgs) {
      // TODO
    }
  }

  @Override
  public ParagraphJobListener getParagraphJobListener(Note note) {
    return new ParagraphListenerImpl(this, note);
  }

  public NotebookEventListener getNotebookInformationListener() {
    return new NotebookInformationListener(this);
  }

  private void sendAllAngularObjects(Note note, String user, NotebookSocket conn)
      throws IOException {
    List<InterpreterSetting> settings =
        notebook().getInterpreterSettingManager().getInterpreterSettings(note.getId());
    if (settings == null || settings.size() == 0) {
      return;
    }

    for (InterpreterSetting intpSetting : settings) {
      AngularObjectRegistry registry =
          intpSetting.getInterpreterGroup(user, note.getId()).getAngularObjectRegistry();
      List<AngularObject> objects = registry.getAllWithGlobal(note.getId());
      for (AngularObject object : objects) {
        conn.send(serializeMessage(
            new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", object)
                .put("interpreterGroupId",
                    intpSetting.getInterpreterGroup(user, note.getId()).getId())
                .put("noteId", note.getId()).put("paragraphId", object.getParagraphId())));
      }
    }
  }

  @Override
  public void onAdd(String interpreterGroupId, AngularObject object) {
    onUpdate(interpreterGroupId, object);
  }

  @Override
  public void onUpdate(String interpreterGroupId, AngularObject object) {
    Notebook notebook = notebook();
    if (notebook == null) {
      return;
    }

    List<Note> notes = notebook.getAllNotes();
    for (Note note : notes) {
      if (object.getNoteId() != null && !note.getId().equals(object.getNoteId())) {
        continue;
      }

      List<InterpreterSetting> intpSettings =
          notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId());
      if (intpSettings.isEmpty()) {
        continue;
      }

      broadcast(note.getId(), new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", object)
          .put("interpreterGroupId", interpreterGroupId).put("noteId", note.getId())
          .put("paragraphId", object.getParagraphId()));
    }
  }

  @Override
  public void onRemove(String interpreterGroupId, String name, String noteId, String paragraphId) {
    Notebook notebook = notebook();
    List<Note> notes = notebook.getAllNotes();
    for (Note note : notes) {
      if (noteId != null && !note.getId().equals(noteId)) {
        continue;
      }

      List<String> settingIds =
          notebook.getInterpreterSettingManager().getInterpreters(note.getId());
      for (String id : settingIds) {
        if (interpreterGroupId.contains(id)) {
          broadcast(note.getId(),
              new Message(OP.ANGULAR_OBJECT_REMOVE).put("name", name).put("noteId", noteId)
                  .put("paragraphId", paragraphId));
          break;
        }
      }
    }
  }

  private void getEditorSetting(NotebookSocket conn, Message fromMessage) throws IOException {
    String paragraphId = (String) fromMessage.get("paragraphId");
    String replName = (String) fromMessage.get("magic");
    String noteId = getOpenNoteId(conn);
    String user = fromMessage.principal;
    Message resp = new Message(OP.EDITOR_SETTING);
    resp.put("paragraphId", paragraphId);
    Interpreter interpreter =
        notebook().getInterpreterFactory().getInterpreter(user, noteId, replName);
    resp.put("editor", notebook().getInterpreterSettingManager().
        getEditorSetting(interpreter, user, noteId, replName));
    conn.send(serializeMessage(resp));
  }

  private void getInterpreterSettings(NotebookSocket conn, AuthenticationInfo subject)
      throws IOException {
    List<InterpreterSetting> availableSettings = notebook().getInterpreterSettingManager().get();
    conn.send(serializeMessage(
        new Message(OP.INTERPRETER_SETTINGS).put("interpreterSettings", availableSettings)));
  }

  @Override
  public void onMetaInfosReceived(String settingId, Map<String, String> metaInfos) {
    InterpreterSetting interpreterSetting =
        notebook().getInterpreterSettingManager().get(settingId);
    interpreterSetting.setInfos(metaInfos);
  }

  private void switchConnectionToWatcher(NotebookSocket conn, Message messagereceived)
      throws IOException {
    if (!isSessionAllowedToSwitchToWatcher(conn)) {
      LOG.error("Cannot switch this client to watcher, invalid security key");
      return;
    }
    LOG.info("Going to add {} to watcher socket", conn);
    // add the connection to the watcher.
    if (watcherSockets.contains(conn)) {
      LOG.info("connection alrerady present in the watcher");
      return;
    }
    watcherSockets.add(conn);

    // remove this connection from regular zeppelin ws usage.
    removeConnectionFromAllNote(conn);
    connectedSockets.remove(conn);
    removeUserConnection(conn.getUser(), conn);
  }

  private boolean isSessionAllowedToSwitchToWatcher(NotebookSocket session) {
    String watcherSecurityKey = session.getRequest().getHeader(WatcherSecurityKey.HTTP_HEADER);
    return !(StringUtils.isBlank(watcherSecurityKey) || !watcherSecurityKey
        .equals(WatcherSecurityKey.getKey()));
  }

  /**
   * Send websocket message to all connections regardless of notebook id
   */
  private void broadcastToAllConnections(String serialized) {
    broadcastToAllConnectionsExcept(null, serialized);
  }

  private void broadcastToAllConnectionsExcept(NotebookSocket exclude, String serialized) {
    synchronized (connectedSockets) {
      for (NotebookSocket conn: connectedSockets) {
        if (exclude != null && exclude.equals(conn)) {
          continue;
        }

        try {
          conn.send(serialized);
        } catch (IOException e) {
          LOG.error("Cannot broadcast message to watcher", e);
        }
      }
    }
  }

  private void broadcastToWatchers(String noteId, String subject, Message message) {
    synchronized (watcherSockets) {
      for (NotebookSocket watcher : watcherSockets) {
        try {
          watcher.send(
              WatcherMessage.builder(noteId).subject(subject).message(serializeMessage(message))
                  .build().serialize());
        } catch (IOException e) {
          LOG.error("Cannot broadcast message to watcher", e);
        }
      }
    }
  }

  @Override
  public void onParaInfosReceived(String noteId, String paragraphId,
      String interpreterSettingId, Map<String, String> metaInfos) {
    Note note = notebook().getNote(noteId);
    if (note != null) {
      Paragraph paragraph = note.getParagraph(paragraphId);
      if (paragraph != null) {
        InterpreterSetting setting = notebook().getInterpreterSettingManager()
            .get(interpreterSettingId);
        setting.addNoteToPara(noteId, paragraphId);
        String label = metaInfos.get("label");
        String tooltip = metaInfos.get("tooltip");
        List<String> keysToRemove = Arrays.asList("noteId", "paraId", "label", "tooltip");
        for (String removeKey : keysToRemove) {
          metaInfos.remove(removeKey);
        }
        paragraph
            .updateRuntimeInfos(label, tooltip, metaInfos, setting.getGroup(), setting.getId());
        broadcast(
            note.getId(),
            new Message(OP.PARAS_INFO).put("id", paragraphId).put("infos",
                paragraph.getRuntimeInfos()));
      }
    }
  }

  public void clearParagraphRuntimeInfo(InterpreterSetting setting) {
    Map<String, Set<String>> noteIdAndParaMap = setting.getNoteIdAndParaMap();
    if (noteIdAndParaMap != null && !noteIdAndParaMap.isEmpty()) {
      for (String noteId : noteIdAndParaMap.keySet()) {
        Set<String> paraIdSet = noteIdAndParaMap.get(noteId);
        if (paraIdSet != null && !paraIdSet.isEmpty()) {
          for (String paraId : paraIdSet) {
            Note note = notebook().getNote(noteId);
            if (note != null) {
              Paragraph paragraph = note.getParagraph(paraId);
              if (paragraph != null) {
                paragraph.clearRuntimeInfo(setting.getId());
                broadcast(noteId, new Message(OP.PARAGRAPH).put("paragraph", paragraph));
              }
            }
          }
        }
      }
    }
    setting.clearNoteIdAndParaMap();
  }
}


text:
<!doctype html>

abc def

abcdefghi jklmnopqr

redpajama_set_name: RedPajamaGithub


text:
import os

from behave_cli import environment as clienv

from distutils.util import strtobool as _bool
BEHAVE_DEBUG_ON_ERROR = _bool(os.environ.get("BEHAVE_DEBUG_ON_ERROR", "no"))

def after_step(context, step):
    if BEHAVE_DEBUG_ON_ERROR and step.status == "failed":
        import ipdb
        ipdb.post_mortem(step.exc_traceback)

def before_all(context):
    clienv.before_all(context)

def after_all(context):
    clienv.after_all(context)

def before_feature(context, feature):
    clienv.before_feature(context, feature)

def after_feature(context, feature):
    clienv.after_feature(context, feature)

def before_scenario(context, scenario):
    clienv.before_scenario(context, scenario)

def after_scenario(context, scenario):
    clienv.after_scenario(context, scenario)


text:
$('.jsFormRedactChange').click(function (e) {
  e.preventDefault();
  var $this = $(this),
      currentValue = $this.closest('.jsFormRedact').find('.jsFormRedactView').html();

  $this.closest('.jsFormRedact').find('.jsFormRedactControl, .jsFormRedactView').addClass('_hidden');
  $this.closest('.jsFormRedact').find('.jsFormRedactHidden').addClass('_visible');
  $this.closest('.jsFormRedact').find('.jsFormRedactField').val(currentValue).focus();
});

$('.jsFormRedactCancel').click(function (e) {
  e.preventDefault();
  var $this = $(this);

  $this.closest('.jsFormRedact').find('.jsFormRedactControl, .jsFormRedactView').removeClass('_hidden');
  $this.closest('.jsFormRedact').find('.jsFormRedactHidden').removeClass('_visible');
});


text:
import os
import time
from datetime import datetime

from trac.config import IntOption
from trac.core import *
from trac.env import open_environment
from trac.resource import ResourceNotFound
from trac.util.datefmt import localtz, to_datetime
from trac.versioncontrol.api import DbRepositoryProvider

from tracportal.api import IProjectListProvider
from tracportal.core import get_trac_env_parent_dir
from tracportal.i18n import _
from tracportal.project_list.model import ProjectActivity, ProjectInfo
from tracportal.util import to_tractime

class IProjectActivityDataProvider(Interface):
    """Extension point interface for adding sources for project activity data
    project_list."""

    @staticmethod
    def get_activity_data(env_name, beginning, end):
        """Return activity data of the project."""

class DefaultActivityDataProvider(Component):
    implements(IProjectActivityDataProvider)
    project_list_providers = ExtensionPoint(IProjectListProvider)

    # Trac options
    period = IntOption('tracportal', 'project_activity_assessment_period', 30,
                       doc=_('Number of days to evaluate the project activity.'))

    # IProjectActivityDataProvider methods
    def get_activity_data(self, env_name, beginning, end):
        """Return activity data of the local project."""
        activity = None
        try:
            activity = ProjectActivity(self.env, env_name)
        except ResourceNotFound, e:
            self.log.debug(e)
            activity = ProjectActivity(self.env)
        if not activity.is_expired():
            return activity
        env = None
        parent_dir = get_trac_env_parent_dir(self.env)
        env_dir = os.path.join(parent_dir, env_name)
        try:
            env = open_environment(env_dir, True)
        except Exception, e:
            # if needs_upgrade is True, raise Exception.
            self.log.warn("Cannot load a project Environment '%s', "
                          "Error: %s" % (env_dir, e))
            return None
        (activity.changes, activity.changes_month) = self._get_rev_count(env, beginning, end)
        (activity.tickets, activity.tickets_closed, activity.tickets_month,
         activity.tickets_closed_month) = self._get_ticket_count(env, beginning, end)
        if activity.exists:
            activity.update()
        else:
            activity.env_name = env_name
            activity.insert()
        return activity

    # Internal methods
    def _get_rev_count(self, env, beginning, end):
        revisions = 0
        total_revisions = 0
        provider = DbRepositoryProvider(env)
        repos = provider.get_repositories()
        for r in repos:
            repo = None
            if len(r) is 2 and 'alias' not in r[1]:
                repo = env.get_repository(r[0])
            if repo:
                try:
                    for changeset in repo.get_changesets(to_datetime(0), end):
                        if changeset.date > beginning:
                            revisions += 1
                        total_revisions += 1
                except Exception, e:
                    self.log.warn('%s' % str(e))
        return total_revisions, revisions

    @staticmethod
    def _get_ticket_count(env, beginning, end):
        if isinstance(beginning, datetime):
            beginning = time.mktime(beginning.astimezone(localtz).timetuple())
        if isinstance(end, datetime):
            end = time.mktime(end.astimezone(localtz).timetuple())
        beginning = to_tractime(beginning)
        end = to_tractime(end)
        db = env.get_read_db()
        tickets = 0
        tickets_month = 0
        tickets_closed = 0
        tickets_closed_month = 0

        def get_one(sql):
            cursor = db.cursor()
            cursor.execute(sql)
            return cursor.fetchone()[0]

        # count tickets
        tickets = get_one("SELECT count(*) FROM ticket")
        # count closed tickets
        tickets_closed = get_one("SELECT count(*) FROM ticket_change "
                                 "WHERE field='status' AND newvalue='closed'")
        tickets_reopened = get_one("SELECT count(*) FROM ticket_change "
                                   "WHERE field='status' AND newvalue='reopened'")
        tickets_closed -= tickets_reopened
        # count tickets during a term.
        tickets_month = get_one("SELECT count(*) FROM ticket WHERE time>=%s AND time<=%s" % (beginning, end))
        # count closed ticket during a term.
        tickets_closed_month = get_one("SELECT count(*) FROM ticket_change "
                                       "WHERE field='status' AND newvalue='closed' "
                                       "AND time>=%s AND time <=%s" % (beginning, end))
        tickets_reopened_month = get_one("SELECT count(*) FROM ticket_change "
                                         "WHERE field='status' AND newvalue='reopened' "
                                         "AND time>=%s AND time <=%s" % (beginning, end))
        tickets_closed_month -= tickets_reopened_month
        return tickets, tickets_closed, tickets_month, tickets_closed_month

class IProjectInfoProvider(Interface):
    @staticmethod
    def get_info(env_name):
        """Return the project information.
        Elements:
          `id`: Environment name
          `name`: the project name
          `description`: the project description
          `url`: the project base url
        """

class DefaultProjectInfoProvider(Component):
    implements(IProjectInfoProvider)

    # IProjectInfoProvider methods
    def get_info(self, env_name):
        info = None
        try:
            info = ProjectInfo(self.env, env_name)
        except ResourceNotFound, e:
            self.log.debug(e)
            info = ProjectInfo(self.env)
        if not info.is_expired():
            return info
        env = None
        parent_dir = get_trac_env_parent_dir(self.env)
        env_dir = os.path.join(parent_dir, env_name)
        try:
            env = open_environment(env_dir, True)
        except Exception, e:
            # if needs_upgrade is True, raise Exception.
            self.log.warn("Cannot load a project Environment '%s', "
                          "Error: %s" % (env_dir, e))
            return None
        href = env.abs_href()
        if not href or href == '/':
            href = self.env.abs_href().replace(self.get_self_env_name(), env_name)
        if href.endswith('/'):
            href = href.rstrip('/')
        info.id = env_name
        info.name = env.project_name
        info.description = env.project_description
        info.url = href
        if info.exists:
            info.update()
        else:
            info.env_name = env_name
            info.insert()
        return info

    def get_self_env_name(self):
        return self.env.path.rstrip('/').split('/')[-1]