/*
* 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 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.display.Input;
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")
.registerTypeAdapterFactory(Input.TypeAdapterFactory).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 +
", RECEIVE PRINCIPAL << " + messagereceived.principal +
", RECEIVE TICKET << " + messagereceived.ticket +
", RECEIVE ROLES << " + messagereceived.roles +
", RECEIVE DATA << " + messagereceived.data);
if (LOG.isTraceEnabled()) {
LOG.trace("RECEIVE MSG = " + messagereceived);
}
String ticket = TicketContainer.instance.getTicket(messagereceived.principal);
if (ticket != null &&
(messagereceived.ticket == null || !ticket.equals(messagereceived.ticket))) {
/* not to pollute logs, log instead of exception */
if (StringUtils.isEmpty(messagereceived.ticket)) {
LOG.debug("{} message: invalid ticket {} != {}", messagereceived.op,
messagereceived.ticket, ticket);
} else {
if (!messagereceived.op.equals(OP.PING)) {
conn.send(serializeMessage(new Message(OP.SESSION_LOGOUT).put("info",
"Your ticket is invalid possibly due to server restart. "
+ "Please login again.")));
}
}
return;
}
ZeppelinConfiguration conf = ZeppelinConfiguration.create();
boolean allowAnonymous = conf.isAnonymousAllowed();
if (!allowAnonymous && messagereceived.principal.equals("anonymous")) {
throw new Exception("Anonymous access not allowed ");
}
HashSet<String> userAndRoles = new HashSet<>();
userAndRoles.add(messagereceived.principal);
if (!messagereceived.roles.equals("")) {
HashSet<String> roles =
gson.fromJson(messagereceived.roles, new TypeToken<HashSet<String>>() {
}.getType());
if (roles != null) {
userAndRoles.addAll(roles);
}
}
if (StringUtils.isEmpty(conn.getUser())) {
addUserConnection(messagereceived.principal, conn);
}
AuthenticationInfo subject =
new AuthenticationInfo(messagereceived.principal, messagereceived.ticket);
/** Lets be elegant here */
switch (messagereceived.op) {
case LIST_NOTES:
unicastNoteList(conn, subject, userAndRoles);
break;
case RELOAD_NOTES_FROM_REPO:
broadcastReloadedNoteList(subject, userAndRoles);
break;
case GET_HOME_NOTE:
sendHomeNote(conn, userAndRoles, notebook, messagereceived);
break;
case GET_NOTE:
sendNote(conn, userAndRoles, notebook, messagereceived);
break;
case NEW_NOTE:
createNote(conn, userAndRoles, notebook, messagereceived);
break;
case DEL_NOTE:
removeNote(conn, userAndRoles, notebook, messagereceived);
break;
case REMOVE_FOLDER:
removeFolder(conn, userAndRoles, notebook, messagereceived);
break;
case MOVE_NOTE_TO_TRASH:
moveNoteToTrash(conn, userAndRoles, notebook, messagereceived);
break;
case MOVE_FOLDER_TO_TRASH:
moveFolderToTrash(conn, userAndRoles, notebook, messagereceived);
break;
case EMPTY_TRASH:
emptyTrash(conn, userAndRoles, notebook, messagereceived);
break;
case RESTORE_FOLDER:
restoreFolder(conn, userAndRoles, notebook, messagereceived);
break;
case RESTORE_NOTE:
restoreNote(conn, userAndRoles, notebook, messagereceived);
break;
case RESTORE_ALL:
restoreAll(conn, userAndRoles, notebook, messagereceived);
break;
case CLONE_NOTE:
cloneNote(conn, userAndRoles, notebook, messagereceived);
break;
case IMPORT_NOTE:
importNote(conn, userAndRoles, notebook, messagereceived);
break;
case COMMIT_PARAGRAPH:
updateParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case RUN_PARAGRAPH:
runParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case PARAGRAPH_EXECUTED_BY_SPELL:
broadcastSpellExecution(conn, userAndRoles, notebook, messagereceived);
break;
case RUN_ALL_PARAGRAPHS:
runAllParagraphs(conn, userAndRoles, notebook, messagereceived);
break;
case CANCEL_PARAGRAPH:
cancelParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case MOVE_PARAGRAPH:
moveParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case INSERT_PARAGRAPH:
insertParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case COPY_PARAGRAPH:
copyParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case PARAGRAPH_REMOVE:
removeParagraph(conn, userAndRoles, notebook, messagereceived);
break;
case PARAGRAPH_CLEAR_OUTPUT:
clearParagraphOutput(conn, userAndRoles, notebook, messagereceived);
break;
case PARAGRAPH_CLEAR_ALL_OUTPUT:
clearAllParagraphOutput(conn, userAndRoles, notebook, messagereceived);
break;
case NOTE_UPDATE:
updateNote(conn, userAndRoles, notebook, messagereceived);
break;
case NOTE_RENAME:
renameNote(conn, userAndRoles, notebook, messagereceived);
break;
case FOLDER_RENAME:
renameFolder(conn, userAndRoles, notebook, messagereceived);
break;
case UPDATE_PERSONALIZED_MODE:
updatePersonalizedMode(conn, userAndRoles, notebook, messagereceived);
break;
case COMPLETION:
completion(conn, userAndRoles, notebook, messagereceived);
break;
case PING:
break; //do nothing
case ANGULAR_OBJECT_UPDATED:
angularObjectUpdated(conn, userAndRoles, notebook, messagereceived);
break;
case ANGULAR_OBJECT_CLIENT_BIND:
angularObjectClientBind(conn, userAndRoles, notebook, messagereceived);
break;
case ANGULAR_OBJECT_CLIENT_UNBIND:
angularObjectClientUnbind(conn, userAndRoles, notebook, messagereceived);
break;
case LIST_CONFIGURATIONS:
sendAllConfigurations(conn, userAndRoles, notebook);
break;
case CHECKPOINT_NOTE:
checkpointNote(conn, notebook, messagereceived);
break;
case LIST_REVISION_HISTORY:
listRevisionHistory(conn, notebook, messagereceived);
break;
case SET_NOTE_REVISION:
setNoteRevision(conn, userAndRoles, notebook, messagereceived);
break;
case NOTE_REVISION:
getNoteByRevision(conn, notebook, messagereceived);
break;
case LIST_NOTE_JOBS:
unicastNoteJobInfo(conn, messagereceived);
break;
case UNSUBSCRIBE_UPDATE_NOTE_JOBS:
unsubscribeNoteJobInfo(conn);
break;
case GET_INTERPRETER_BINDINGS:
getInterpreterBindings(conn, messagereceived);
break;
case SAVE_INTERPRETER_BINDINGS:
saveInterpreterBindings(conn, messagereceived);
break;
case EDITOR_SETTING:
getEditorSetting(conn, messagereceived);
break;
case GET_INTERPRETER_SETTINGS:
getInterpreterSettings(conn, subject);
break;
case WATCHER:
switchConnectionToWatcher(conn, messagereceived);
break;
default:
break;
}
} catch (Exception e) {
LOG.error("Can't handle message", e);
}
}
@Override
public void onClose(NotebookSocket conn, int code, String reason) {
LOG.info("Closed connection to {} : {}. ({}) {}", conn.getRequest().getRemoteAddr(),
conn.getRequest().getRemotePort(), code, reason);
removeConnectionFromAllNote(conn);
connectedSockets.remove(conn);
removeUserConnection(conn.getUser(), conn);
}
private void removeUserConnection(String user, NotebookSocket conn) {
if (userConnectedSockets.containsKey(user)) {
userConnectedSockets.get(user).remove(conn);
} else {
LOG.warn("Closing connection that is absent in user connections");
}
}
private void addUserConnection(String user, NotebookSocket conn) {
conn.setUser(user);
if (userConnectedSockets.containsKey(user)) {
userConnectedSockets.get(user).add(conn);
} else {
Queue<NotebookSocket> socketQueue = new ConcurrentLinkedQueue<>();
socketQueue.add(conn);
userConnectedSockets.put(user, socketQueue);
}
}
protected Message deserializeMessage(String msg) {
return gson.fromJson(msg, Message.class);
}
protected String serializeMessage(Message m) {
return gson.toJson(m);
}
private void addConnectionToNote(String noteId, NotebookSocket socket) {
synchronized (noteSocketMap) {
removeConnectionFromAllNote(socket); // make sure a socket relates only a
// single note.
List<NotebookSocket> socketList = noteSocketMap.get(noteId);
if (socketList == null) {
socketList = new LinkedList<>();
noteSocketMap.put(noteId, socketList);
}
if (!socketList.contains(socket)) {
socketList.add(socket);
}
}
}
private void removeConnectionFromNote(String noteId, NotebookSocket socket) {
synchronized (noteSocketMap) {
List<NotebookSocket> socketList = noteSocketMap.get(noteId);
if (socketList != null) {
socketList.remove(socket);
}
}
}
private void removeNote(String noteId) {
synchronized (noteSocketMap) {
List<NotebookSocket> socketList = noteSocketMap.remove(noteId);
}
}
private void removeConnectionFromAllNote(NotebookSocket socket) {
synchronized (noteSocketMap) {
Set<String> keys = noteSocketMap.keySet();
for (String noteId : keys) {
removeConnectionFromNote(noteId, socket);
}
}
}
private String getOpenNoteId(NotebookSocket socket) {
String id = null;
synchronized (noteSocketMap) {
Set<String> keys = noteSocketMap.keySet();
for (String noteId : keys) {
List<NotebookSocket> sockets = noteSocketMap.get(noteId);
if (sockets.contains(socket)) {
id = noteId;
}
}
}
return id;
}
private void broadcastToNoteBindedInterpreter(String interpreterGroupId, Message m) {
Notebook notebook = notebook();
List<Note> notes = notebook.getAllNotes();
for (Note note : notes) {
List<String> ids = notebook.getInterpreterSettingManager().getInterpreters(note.getId());
for (String id : ids) {
if (id.equals(interpreterGroupId)) {
broadcast(note.getId(), m);
}
}
}
}
private void broadcast(String noteId, Message m) {
synchronized (noteSocketMap) {
broadcastToWatchers(noteId, StringUtils.EMPTY, m);
List<NotebookSocket> socketLists = noteSocketMap.get(noteId);
if (socketLists == null || socketLists.size() == 0) {
return;
}
LOG.debug("SEND >> " + m);
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);
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 unicastParagraph(Note note, Paragraph p, String user) {
if (!note.isPersonalizedMode() || p == null || user == null) {
return;
}
if (!userConnectedSockets.containsKey(user)) {
LOG.warn("Failed to send unicast. user {} that is not in connections map", user);
return;
}
for (NotebookSocket conn : userConnectedSockets.get(user)) {
Message m = new Message(OP.PARAGRAPH).put("paragraph", p);
unicast(m, conn);
}
}
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.addNewParagraph(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");
notebook.moveNoteToTrash(note.getId());
}
}
private void moveFolderToTrash(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws SchedulerException, IOException {
String folderId = (String) fromMessage.get("id");
if (folderId == null) {
return;
}
Folder folder = notebook.getFolder(folderId);
if (folder != null && !folder.isTrash()) {
String trashFolderId = Folder.TRASH_FOLDER_ID + "/" + folderId;
if (notebook.hasFolder(trashFolderId)){
DateTime currentDate = new DateTime();
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
trashFolderId += Folder.TRASH_FOLDER_CONFLICT_INFIX + formatter.print(currentDate);
}
fromMessage.put("name", trashFolderId);
renameFolder(conn, userAndRoles, notebook, fromMessage, "move");
}
}
private void restoreNote(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws SchedulerException, IOException {
String noteId = (String) fromMessage.get("id");
if (noteId == null) {
return;
}
Note note = notebook.getNote(noteId);
if (note != null && note.isTrash()) {
fromMessage.put("name", note.getName().replaceFirst(Folder.TRASH_FOLDER_ID + "/", ""));
renameNote(conn, userAndRoles, notebook, fromMessage, "restore");
}
}
private void restoreFolder(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws SchedulerException, IOException {
String folderId = (String) fromMessage.get("id");
if (folderId == null) {
return;
}
Folder folder = notebook.getFolder(folderId);
if (folder != null && folder.isTrash()) {
String restoreName = folder.getId().replaceFirst(Folder.TRASH_FOLDER_ID + "/", "").trim();
// if the folder had conflict when it had moved to trash before
Pattern p = Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}$");
Matcher m = p.matcher(restoreName);
restoreName = m.replaceAll("").trim();
fromMessage.put("name", restoreName);
renameFolder(conn, userAndRoles, notebook, fromMessage, "restore");
}
}
private void restoreAll(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws SchedulerException, IOException {
Folder trashFolder = notebook.getFolder(Folder.TRASH_FOLDER_ID);
if (trashFolder != null) {
fromMessage.data = new HashMap<>();
fromMessage.put("id", Folder.TRASH_FOLDER_ID);
fromMessage.put("name", Folder.ROOT_FOLDER_ID);
renameFolder(conn, userAndRoles, notebook, fromMessage, "restore trash");
}
}
private void emptyTrash(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage)
throws SchedulerException, IOException {
fromMessage.data = new HashMap<>();
fromMessage.put("id", Folder.TRASH_FOLDER_ID);
removeFolder(conn, userAndRoles, notebook, fromMessage);
}
private void updateParagraph(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
return;
}
Map<String, Object> params = (Map<String, Object>) fromMessage.get("params");
Map<String, Object> config = (Map<String, Object>) fromMessage.get("config");
String noteId = getOpenNoteId(conn);
if (!hasParagraphWriterPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "write")) {
return;
}
final Note note = notebook.getNote(noteId);
Paragraph p = note.getParagraph(paragraphId);
p.settings.setParams(params);
p.setConfig(config);
p.setTitle((String) fromMessage.get("title"));
p.setText((String) fromMessage.get("paragraph"));
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
if (note.isPersonalizedMode()) {
p = p.getUserParagraph(subject.getUser());
p.settings.setParams(params);
p.setConfig(config);
p.setTitle((String) fromMessage.get("title"));
p.setText((String) fromMessage.get("paragraph"));
}
note.persist(subject);
if (note.isPersonalizedMode()) {
Map<String, Paragraph> userParagraphMap =
note.getParagraph(paragraphId).getUserParagraphMap();
broadcastParagraphs(userParagraphMap, p);
} else {
broadcastParagraph(note, p);
}
}
private void cloneNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException, CloneNotSupportedException {
String noteId = getOpenNoteId(conn);
String name = (String) fromMessage.get("name");
Note newNote = notebook.cloneNote(noteId, name, new AuthenticationInfo(fromMessage.principal));
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
addConnectionToNote(newNote.getId(), (NotebookSocket) conn);
conn.send(serializeMessage(new Message(OP.NEW_NOTE).put("note", newNote)));
broadcastNoteList(subject, userAndRoles);
}
private void clearAllParagraphOutput(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
final String noteId = (String) fromMessage.get("id");
if (StringUtils.isBlank(noteId)) {
return;
}
if (!hasParagraphWriterPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "clear output")) {
return;
}
Note note = notebook.getNote(noteId);
note.clearAllParagraphOutput();
broadcastNote(note);
}
protected Note importNote(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
Note note = null;
if (fromMessage != null) {
String noteName = (String) ((Map) fromMessage.get("note")).get("name");
String noteJson = gson.toJson(fromMessage.get("note"));
AuthenticationInfo subject = null;
if (fromMessage.principal != null) {
subject = new AuthenticationInfo(fromMessage.principal);
} else {
subject = new AuthenticationInfo("anonymous");
}
note = notebook.importNote(noteJson, noteName, subject);
note.persist(subject);
broadcastNote(note);
broadcastNoteList(subject, userAndRoles);
}
return note;
}
private void removeParagraph(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
return;
}
String noteId = getOpenNoteId(conn);
if (!hasParagraphWriterPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "write")) {
return;
}
/** We dont want to remove the last paragraph */
final Note note = notebook.getNote(noteId);
if (!note.isLastParagraph(paragraphId)) {
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
Paragraph para = note.removeParagraph(subject.getUser(), paragraphId);
note.persist(subject);
if (para != null) {
broadcast(note.getId(), new Message(OP.PARAGRAPH_REMOVED).
put("id", para.getId()));
}
}
}
private void clearParagraphOutput(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook, Message fromMessage) throws IOException {
final String paragraphId = (String) fromMessage.get("id");
if (paragraphId == null) {
return;
}
String noteId = getOpenNoteId(conn);
if (!hasParagraphWriterPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "write")) {
return;
}
final Note note = notebook.getNote(noteId);
if (note.isPersonalizedMode()) {
String user = fromMessage.principal;
Paragraph p = note.clearPersonalizedParagraphOutput(paragraphId, user);
unicastParagraph(note, p, user);
} else {
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.insertNewParagraph(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;
}
// 1. clear paragraph only if personalized,
// otherwise this will be handed in `onOutputClear`
final Note note = notebook.getNote(noteId);
if (note.isPersonalizedMode()) {
String user = fromMessage.principal;
Paragraph p = note.clearPersonalizedParagraphOutput(paragraphId, user);
unicastParagraph(note, p, user);
}
// 2. set paragraph values
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");
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 not empty, let's add a new one
boolean isTheLastParagraph = note.isLastParagraph(p.getId());
if (!(Strings.isNullOrEmpty(p.getText()) ||
p.getText().trim().equals(p.getMagic())) &&
isTheLastParagraph) {
Paragraph newPara = note.addNewParagraph(p.getAuthenticationInfo());
broadcastNewParagraph(note, newPara);
}
}
/**
* @return false if failed to save a note
*/
private boolean persistNoteWithAuthInfo(NotebookSocket conn,
Note note, Paragraph p) throws IOException {
try {
note.persist(p.getAuthenticationInfo());
return true;
} catch (FileSystemException ex) {
LOG.error("Exception from run", ex);
conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
"Oops! There is something wrong with the notebook file system. "
+ "Please check the logs for more details.")));
// don't run the paragraph when there is error on persisting the note information
return false;
}
}
private void persistAndExecuteSingleParagraph(NotebookSocket conn,
Note note, Paragraph p) throws IOException {
addNewParagraphIfLastParagraphIsExecuted(note, p);
if (!persistNoteWithAuthInfo(conn, note, p)) {
return;
}
try {
note.run(p.getId());
} catch (Exception ex) {
LOG.error("Exception from run", ex);
if (p != null) {
p.setReturn(new InterpreterResult(InterpreterResult.Code.ERROR, ex.getMessage()), ex);
p.setStatus(Status.ERROR);
broadcast(note.getId(), new Message(OP.PARAGRAPH).put("paragraph", p));
}
}
}
private Paragraph setParagraphUsingMessage(Note note, Message fromMessage, String paragraphId,
String text, String title, Map<String, Object> params,
Map<String, Object> config) {
Paragraph p = note.getParagraph(paragraphId);
p.setText(text);
p.setTitle(title);
AuthenticationInfo subject =
new AuthenticationInfo(fromMessage.principal, fromMessage.ticket);
p.setAuthenticationInfo(subject);
p.settings.setParams(params);
p.setConfig(config);
if (note.isPersonalizedMode()) {
p = note.getParagraph(paragraphId);
p.setText(text);
p.setTitle(title);
p.setAuthenticationInfo(subject);
p.settings.setParams(params);
p.setConfig(config);
}
return p;
}
private void sendAllConfigurations(NotebookSocket conn, HashSet<String> userAndRoles,
Notebook notebook) throws IOException {
ZeppelinConfiguration conf = notebook.getConf();
Map<String, String> configurations =
conf.dumpConfigurations(conf, new ZeppelinConfiguration.ConfigurationKeyPredicate() {
@Override
public boolean apply(String key) {
return !key.contains("password") && !key.equals(
ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_AZURE_CONNECTION_STRING
.getVarName());
}
});
conn.send(serializeMessage(
new Message(OP.CONFIGURATIONS_INFO).put("configurations", configurations)));
}
private void checkpointNote(NotebookSocket conn, Notebook notebook, Message fromMessage)
throws IOException {
String noteId = (String) fromMessage.get("noteId");
String commitMessage = (String) fromMessage.get("commitMessage");
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
Revision revision = notebook.checkpointNote(noteId, commitMessage, subject);
if (!Revision.isEmpty(revision)) {
List<Revision> revisions = notebook.listRevisionHistory(noteId, subject);
conn.send(
serializeMessage(new Message(OP.LIST_REVISION_HISTORY).put("revisionList", revisions)));
} else {
conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
"Couldn't checkpoint note revision: possibly storage doesn't support versioning. "
+ "Please check the logs for more details.")));
}
}
private void listRevisionHistory(NotebookSocket conn, Notebook notebook, Message fromMessage)
throws IOException {
String noteId = (String) fromMessage.get("noteId");
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
List<Revision> revisions = notebook.listRevisionHistory(noteId, subject);
conn.send(
serializeMessage(new Message(OP.LIST_REVISION_HISTORY).put("revisionList", revisions)));
}
private void setNoteRevision(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook,
Message fromMessage) throws IOException {
String noteId = (String) fromMessage.get("noteId");
String revisionId = (String) fromMessage.get("revisionId");
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
if (!hasParagraphWriterPermission(conn, notebook, noteId,
userAndRoles, fromMessage.principal, "update")) {
return;
}
Note headNote = null;
boolean setRevisionStatus;
try {
headNote = notebook.setNoteRevision(noteId, revisionId, subject);
setRevisionStatus = headNote != null;
} catch (Exception e) {
setRevisionStatus = false;
LOG.error("Failed to set given note revision", e);
}
if (setRevisionStatus) {
notebook.loadNoteFromRepo(noteId, subject);
}
conn.send(serializeMessage(new Message(OP.SET_NOTE_REVISION).put("status", setRevisionStatus)));
if (setRevisionStatus) {
Note reloadedNote = notebook.getNote(headNote.getId());
broadcastNote(reloadedNote);
} else {
conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info",
"Couldn't set note to the given revision. "
+ "Please check the logs for more details.")));
}
}
private void getNoteByRevision(NotebookSocket conn, Notebook notebook, Message fromMessage)
throws IOException {
String noteId = (String) fromMessage.get("noteId");
String revisionId = (String) fromMessage.get("revisionId");
AuthenticationInfo subject = new AuthenticationInfo(fromMessage.principal);
Note revisionNote = notebook.getNoteByRevision(noteId, revisionId, subject);
conn.send(serializeMessage(
new Message(OP.NOTE_REVISION).put("noteId", noteId).put("revisionId", revisionId)
.put("note", revisionNote)));
}
/**
* This callback is for the paragraph that runs on ZeppelinServer
*
* @param output output to append
*/
@Override
public void onOutputAppend(String noteId, String paragraphId, int index, String output) {
Message msg = new Message(OP.PARAGRAPH_APPEND_OUTPUT).put("noteId", noteId)
.put("paragraphId", paragraphId).put("index", index).put("data", output);
broadcast(noteId, msg);
}
/**
* This callback is for the paragraph that runs on ZeppelinServer
*
* @param output output to update (replace)
*/
@Override
public void onOutputUpdated(String noteId, String paragraphId, int index,
InterpreterResult.Type type, String output) {
Message msg = new Message(OP.PARAGRAPH_UPDATE_OUTPUT).put("noteId", noteId)
.put("paragraphId", paragraphId).put("index", index).put("type", type).put("data", output);
Note note = notebook().getNote(noteId);
if (note.isPersonalizedMode()) {
String user = note.getParagraph(paragraphId).getUser();
if (null != user) {
multicastToUser(user, msg);
}
} else {
broadcast(noteId, msg);
}
}
/**
* This callback is for the paragraph that runs on ZeppelinServer
*/
@Override
public void onOutputClear(String noteId, String paragraphId) {
Notebook notebook = notebook();
final Note note = notebook.getNote(noteId);
note.clearParagraphOutput(paragraphId);
Paragraph paragraph = note.getParagraph(paragraphId);
broadcastParagraph(note, paragraph);
}
/**
* When application append output
*/
@Override
public void onOutputAppend(String noteId, String paragraphId, int index, String appId,
String output) {
Message msg =
new Message(OP.APP_APPEND_OUTPUT).put("noteId", noteId).put("paragraphId", paragraphId)
.put("index", index).put("appId", appId).put("data", output);
broadcast(noteId, msg);
}
/**
* When application update output
*/
@Override
public void onOutputUpdated(String noteId, String paragraphId, int index, String appId,
InterpreterResult.Type type, String output) {
Message msg =
new Message(OP.APP_UPDATE_OUTPUT).put("noteId", noteId).put("paragraphId", paragraphId)
.put("index", index).put("type", type).put("appId", appId).put("data", output);
broadcast(noteId, msg);
}
@Override
public void onLoad(String noteId, String paragraphId, String appId, HeliumPackage pkg) {
Message msg = new Message(OP.APP_LOAD).put("noteId", noteId).put("paragraphId", paragraphId)
.put("appId", appId).put("pkg", pkg);
broadcast(noteId, msg);
}
@Override
public void onStatusChange(String noteId, String paragraphId, String appId, String status) {
Message msg =
new Message(OP.APP_STATUS_CHANGE).put("noteId", noteId).put("paragraphId", paragraphId)
.put("appId", appId).put("status", status);
broadcast(noteId, msg);
}
@Override
public void onGetParagraphRunners(String noteId, String paragraphId,
RemoteWorksEventListener callback) {
Notebook notebookIns = notebook();
List<InterpreterContextRunner> runner = new LinkedList<>();
if (notebookIns == null) {
LOG.info("intepreter request notebook instance is null");
callback.onFinished(notebookIns);
}
try {
Note note = notebookIns.getNote(noteId);
if (note != null) {
if (paragraphId != null) {
Paragraph paragraph = note.getParagraph(paragraphId);
if (paragraph != null) {
runner.add(paragraph.getInterpreterContextRunner());
}
} else {
for (Paragraph p : note.getParagraphs()) {
runner.add(p.getInterpreterContextRunner());
}
}
}
callback.onFinished(runner);
} catch (NullPointerException e) {
LOG.warn(e.getMessage());
callback.onError();
}
}
@Override
public void onRemoteRunParagraph(String noteId, String paragraphId) throws Exception {
Notebook notebookIns = notebook();
try {
if (notebookIns == null) {
throw new Exception("onRemoteRunParagraph notebook instance is null");
}
Note noteIns = notebookIns.getNote(noteId);
if (noteIns == null) {
throw new Exception(String.format("Can't found note id %s", noteId));
}
Paragraph paragraph = noteIns.getParagraph(paragraphId);
if (paragraph == null) {
throw new Exception(String.format("Can't found paragraph %s %s", noteId, paragraphId));
}
Set<String> userAndRoles = Sets.newHashSet();
userAndRoles.add(SecurityUtils.getPrincipal());
userAndRoles.addAll(SecurityUtils.getRoles());
if (!notebookIns.getNotebookAuthorization().hasWriteAuthorization(userAndRoles, noteId)) {
throw new ForbiddenException(String.format("can't execute note %s", noteId));
}
AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
paragraph.setAuthenticationInfo(subject);
noteIns.run(paragraphId);
} catch (Exception e) {
throw e;
}
}
/**
* Notebook Information Change event
*/
public static class NotebookInformationListener implements NotebookEventListener {
private NotebookServer notebookServer;
public NotebookInformationListener(NotebookServer notebookServer) {
this.notebookServer = notebookServer;
}
@Override
public void onParagraphRemove(Paragraph p) {
try {
notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
} catch (IOException ioe) {
LOG.error("can not broadcast for job manager {}", ioe.getMessage());
}
}
@Override
public void onNoteRemove(Note note) {
try {
notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
} catch (IOException ioe) {
LOG.error("can not broadcast for job manager {}", ioe.getMessage());
}
List<Map<String, Object>> notesInfo = new LinkedList<>();
Map<String, Object> info = new HashMap<>();
info.put("noteId", note.getId());
// set paragraphs
List<Map<String, Object>> paragraphsInfo = new LinkedList<>();
// notebook json object root information.
info.put("isRunningJob", false);
info.put("unixTimeLastRun", 0);
info.put("isRemoved", true);
info.put("paragraphs", paragraphsInfo);
notesInfo.add(info);
Map<String, Object> response = new HashMap<>();
response.put("lastResponseUnixTime", System.currentTimeMillis());
response.put("jobs", notesInfo);
notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
}
@Override
public void onParagraphCreate(Paragraph p) {
Notebook notebook = notebookServer.notebook();
List<Map<String, Object>> notebookJobs = notebook.getJobListByParagraphId(p.getId());
Map<String, Object> response = new HashMap<>();
response.put("lastResponseUnixTime", System.currentTimeMillis());
response.put("jobs", notebookJobs);
notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
}
@Override
public void onNoteCreate(Note note) {
Notebook notebook = notebookServer.notebook();
List<Map<String, Object>> notebookJobs = notebook.getJobListByNoteId(note.getId());
Map<String, Object> response = new HashMap<>();
response.put("lastResponseUnixTime", System.currentTimeMillis());
response.put("jobs", notebookJobs);
notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
}
@Override
public void onParagraphStatusChange(Paragraph p, Status status) {
Notebook notebook = notebookServer.notebook();
List<Map<String, Object>> notebookJobs = notebook.getJobListByParagraphId(p.getId());
Map<String, Object> response = new HashMap<>();
response.put("lastResponseUnixTime", System.currentTimeMillis());
response.put("jobs", notebookJobs);
notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
}
@Override
public void onUnbindInterpreter(Note note, InterpreterSetting setting) {
Notebook notebook = notebookServer.notebook();
List<Map<String, Object>> notebookJobs = notebook.getJobListByNoteId(note.getId());
Map<String, Object> response = new HashMap<>();
response.put("lastResponseUnixTime", System.currentTimeMillis());
response.put("jobs", notebookJobs);
notebookServer.broadcast(JOB_MANAGER_SERVICE.JOB_MANAGER_PAGE.getKey(),
new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response));
}
}
/**
* Need description here.
*/
public static class ParagraphListenerImpl implements ParagraphJobListener {
private NotebookServer notebookServer;
private Note note;
public ParagraphListenerImpl(NotebookServer notebookServer, Note note) {
this.notebookServer = notebookServer;
this.note = note;
}
@Override
public void onProgressUpdate(Job job, int progress) {
notebookServer.broadcast(note.getId(),
new Message(OP.PROGRESS).put("id", job.getId()).put("progress", progress));
}
@Override
public void beforeStatusChange(Job job, Status before, Status after) {
}
@Override
public void afterStatusChange(Job job, Status before, Status after) {
if (after == Status.ERROR) {
if (job.getException() != null) {
LOG.error("Error", job.getException());
}
}
if (job.isTerminated()) {
if (job.getStatus() == Status.FINISHED) {
LOG.info("Job {} is finished successfully, status: {}", job.getId(), job.getStatus());
} else {
LOG.warn("Job {} is finished, status: {}, exception: {}, result: {}" , job.getId(),
job.getStatus(), job.getException(), job.getReturn());
}
try {
//TODO(khalid): may change interface for JobListener and pass subject from interpreter
note.persist(job instanceof Paragraph ? ((Paragraph) job).getAuthenticationInfo() : null);
} catch (IOException e) {
LOG.error(e.toString(), e);
}
}
if (job instanceof Paragraph) {
Paragraph p = (Paragraph) job;
p.setStatusToUserParagraph(job.getStatus());
notebookServer.broadcastParagraph(note, p);
}
try {
notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000);
} catch (IOException e) {
LOG.error("can not broadcast for job manager {}", e);
}
}
/**
* This callback is for paragraph that runs on RemoteInterpreterProcess
*/
@Override
public void onOutputAppend(Paragraph paragraph, int idx, String output) {
Message msg =
new Message(OP.PARAGRAPH_APPEND_OUTPUT).put("noteId", paragraph.getNote().getId())
.put("paragraphId", paragraph.getId()).put("data", output);
notebookServer.broadcast(paragraph.getNote().getId(), msg);
}
/**
* This callback is for paragraph that runs on RemoteInterpreterProcess
*/
@Override
public void onOutputUpdate(Paragraph paragraph, int idx, InterpreterResultMessage result) {
String output = result.getData();
Message msg =
new Message(OP.PARAGRAPH_UPDATE_OUTPUT).put("noteId", paragraph.getNote().getId())
.put("paragraphId", paragraph.getId()).put("data", output);
notebookServer.broadcast(paragraph.getNote().getId(), msg);
}
@Override
public void onOutputUpdateAll(Paragraph paragraph, List<InterpreterResultMessage> msgs) {
// TODO
}
}
@Override
public ParagraphJobListener getParagraphJobListener(Note note) {
return new ParagraphListenerImpl(this, note);
}
public NotebookEventListener getNotebookInformationListener() {
return new NotebookInformationListener(this);
}
private void sendAllAngularObjects(Note note, String user, NotebookSocket conn)
throws IOException {
List<InterpreterSetting> settings =
notebook().getInterpreterSettingManager().getInterpreterSettings(note.getId());
if (settings == null || settings.size() == 0) {
return;
}
for (InterpreterSetting intpSetting : settings) {
AngularObjectRegistry registry =
intpSetting.getInterpreterGroup(user, note.getId()).getAngularObjectRegistry();
List<AngularObject> objects = registry.getAllWithGlobal(note.getId());
for (AngularObject object : objects) {
conn.send(serializeMessage(
new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", object)
.put("interpreterGroupId",
intpSetting.getInterpreterGroup(user, note.getId()).getId())
.put("noteId", note.getId()).put("paragraphId", object.getParagraphId())));
}
}
}
@Override
public void onAdd(String interpreterGroupId, AngularObject object) {
onUpdate(interpreterGroupId, object);
}
@Override
public void onUpdate(String interpreterGroupId, AngularObject object) {
Notebook notebook = notebook();
if (notebook == null) {
return;
}
List<Note> notes = notebook.getAllNotes();
for (Note note : notes) {
if (object.getNoteId() != null && !note.getId().equals(object.getNoteId())) {
continue;
}
List<InterpreterSetting> intpSettings =
notebook.getInterpreterSettingManager().getInterpreterSettings(note.getId());
if (intpSettings.isEmpty()) {
continue;
}
broadcast(note.getId(), new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", object)
.put("interpreterGroupId", interpreterGroupId).put("noteId", note.getId())
.put("paragraphId", object.getParagraphId()));
}
}
@Override
public void onRemove(String interpreterGroupId, String name, String noteId, String paragraphId) {
Notebook notebook = notebook();
List<Note> notes = notebook.getAllNotes();
for (Note note : notes) {
if (noteId != null && !note.getId().equals(noteId)) {
continue;
}
List<String> settingIds =
notebook.getInterpreterSettingManager().getInterpreters(note.getId());
for (String id : settingIds) {
if (interpreterGroupId.contains(id)) {
broadcast(note.getId(),
new Message(OP.ANGULAR_OBJECT_REMOVE).put("name", name).put("noteId", noteId)
.put("paragraphId", paragraphId));
break;
}
}
}
}
private void getEditorSetting(NotebookSocket conn, Message fromMessage) throws IOException {
String paragraphId = (String) fromMessage.get("paragraphId");
String replName = (String) fromMessage.get("magic");
String noteId = getOpenNoteId(conn);
String user = fromMessage.principal;
Message resp = new Message(OP.EDITOR_SETTING);
resp.put("paragraphId", paragraphId);
Interpreter interpreter =
notebook().getInterpreterFactory().getInterpreter(user, noteId, replName);
resp.put("editor", notebook().getInterpreterSettingManager().
getEditorSetting(interpreter, user, noteId, replName));
conn.send(serializeMessage(resp));
}
private void getInterpreterSettings(NotebookSocket conn, AuthenticationInfo subject)
throws IOException {
List<InterpreterSetting> availableSettings = notebook().getInterpreterSettingManager().get();
conn.send(serializeMessage(
new Message(OP.INTERPRETER_SETTINGS).put("interpreterSettings", availableSettings)));
}
@Override
public void onMetaInfosReceived(String settingId, Map<String, String> metaInfos) {
InterpreterSetting interpreterSetting =
notebook().getInterpreterSettingManager().get(settingId);
interpreterSetting.setInfos(metaInfos);
}
private void switchConnectionToWatcher(NotebookSocket conn, Message messagereceived)
throws IOException {
if (!isSessionAllowedToSwitchToWatcher(conn)) {
LOG.error("Cannot switch this client to watcher, invalid security key");
return;
}
LOG.info("Going to add {} to watcher socket", conn);
// add the connection to the watcher.
if (watcherSockets.contains(conn)) {
LOG.info("connection alrerady present in the watcher");
return;
}
watcherSockets.add(conn);
// remove this connection from regular zeppelin ws usage.
removeConnectionFromAllNote(conn);
connectedSockets.remove(conn);
removeUserConnection(conn.getUser(), conn);
}
private boolean isSessionAllowedToSwitchToWatcher(NotebookSocket session) {
String watcherSecurityKey = session.getRequest().getHeader(WatcherSecurityKey.HTTP_HEADER);
return !(StringUtils.isBlank(watcherSecurityKey) || !watcherSecurityKey
.equals(WatcherSecurityKey.getKey()));
}
/**
* Send websocket message to all connections regardless of notebook id
*/
private void broadcastToAllConnections(String serialized) {
broadcastToAllConnectionsExcept(null, serialized);
}
private void broadcastToAllConnectionsExcept(NotebookSocket exclude, String serialized) {
synchronized (connectedSockets) {
for (NotebookSocket conn: connectedSockets) {
if (exclude != null && exclude.equals(conn)) {
continue;
}
try {
conn.send(serialized);
} catch (IOException e) {
LOG.error("Cannot broadcast message to watcher", e);
}
}
}
}
private void broadcastToWatchers(String noteId, String subject, Message message) {
synchronized (watcherSockets) {
for (NotebookSocket watcher : watcherSockets) {
try {
watcher.send(
WatcherMessage.builder(noteId).subject(subject).message(serializeMessage(message))
.build().serialize());
} catch (IOException e) {
LOG.error("Cannot broadcast message to watcher", e);
}
}
}
}
@Override
public void onParaInfosReceived(String noteId, String paragraphId,
String interpreterSettingId, Map<String, String> metaInfos) {
Note note = notebook().getNote(noteId);
if (note != null) {
Paragraph paragraph = note.getParagraph(paragraphId);
if (paragraph != null) {
InterpreterSetting setting = notebook().getInterpreterSettingManager()
.get(interpreterSettingId);
setting.addNoteToPara(noteId, paragraphId);
String label = metaInfos.get("label");
String tooltip = metaInfos.get("tooltip");
List<String> keysToRemove = Arrays.asList("noteId", "paraId", "label", "tooltip");
for (String removeKey : keysToRemove) {
metaInfos.remove(removeKey);
}
paragraph
.updateRuntimeInfos(label, tooltip, metaInfos, setting.getGroup(), setting.getId());
broadcast(
note.getId(),
new Message(OP.PARAS_INFO).put("id", paragraphId).put("infos",
paragraph.getRuntimeInfos()));
}
}
}
public void clearParagraphRuntimeInfo(InterpreterSetting setting) {
Map<String, Set<String>> noteIdAndParaMap = setting.getNoteIdAndParaMap();
if (noteIdAndParaMap != null && !noteIdAndParaMap.isEmpty()) {
for (String noteId : noteIdAndParaMap.keySet()) {
Set<String> paraIdSet = noteIdAndParaMap.get(noteId);
if (paraIdSet != null && !paraIdSet.isEmpty()) {
for (String paraId : paraIdSet) {
Note note = notebook().getNote(noteId);
if (note != null) {
Paragraph paragraph = note.getParagraph(paraId);
if (paragraph != null) {
paragraph.clearRuntimeInfo(setting.getId());
broadcast(noteId, new Message(OP.PARAGRAPH).put("paragraph", paragraph));
}
}
}
}
}
}
setting.clearNoteIdAndParaMap();
}
}