org.apache.zeppelin.socket.NotebookServer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.zeppelin.socket.NotebookServer.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
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.ERROR_INFO).put("info",
                                "Your ticket is invalid possibly due to server restart. "
                                        + "Please refresh the page and 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);

        AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
        if (note.isPersonalizedMode()) {
            p = p.getUserParagraphMap().get(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);

        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);
        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) {
                notebookServer.broadcastParagraph(note, (Paragraph) job);
            }
            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));
        return;
    }

    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()));
    }

    private void broadcastToWatchers(String noteId, String subject, Message message) {
        synchronized (watcherSockets) {
            if (watcherSockets.isEmpty()) {
                return;
            }
            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();
    }
}