http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java index d050ab6..c2e99d2 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/service/NotebookService.java @@ -15,26 +15,23 @@ * limitations under the License. */ + package org.apache.zeppelin.service; -import static org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import javax.inject.Inject; +import com.google.common.base.Strings; import org.apache.commons.lang.StringUtils; import org.apache.zeppelin.conf.ZeppelinConfiguration; +import org.apache.zeppelin.display.AngularObject; +import org.apache.zeppelin.display.AngularObjectRegistry; import org.apache.zeppelin.interpreter.Interpreter; import org.apache.zeppelin.interpreter.InterpreterNotFoundException; import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterSetting; import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; -import org.apache.zeppelin.notebook.Folder; import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.NoteInfo; +import org.apache.zeppelin.notebook.NoteManager; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.NotebookAuthorization; import org.apache.zeppelin.notebook.Paragraph; @@ -46,15 +43,41 @@ import org.apache.zeppelin.rest.exception.NoteNotFoundException; import org.apache.zeppelin.rest.exception.ParagraphNotFoundException; import org.apache.zeppelin.scheduler.Job; import org.apache.zeppelin.socket.NotebookServer; +import org.apache.zeppelin.user.AuthenticationInfo; +import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.inject.Inject; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +import static org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN; + + /** - * Service class for Notebook related operations. + * Service class for Notebook related operations. It use {@link Notebook} which provides + * high level api to access notes. + * + * In most of methods, this class will check permission first and whether this note existed. + * If the operation succeeed, {@link ServiceCallback#onSuccess(Object, ServiceContext)} should be + * called, otherwise {@link ServiceCallback#onFailure(Exception, ServiceContext)} should be called. + * */ public class NotebookService { private static final Logger LOGGER = LoggerFactory.getLogger(NotebookService.class); + private static final DateTimeFormatter TRASH_CONFLICT_TIMESTAMP_FORMATTER = + DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); private static NotebookService self; @@ -154,31 +177,49 @@ public class NotebookService { } - public Note createNote(String noteName, + public Note createNote(String notePath, String defaultInterpreterGroup, ServiceContext context, ServiceCallback<Note> callback) throws IOException { + if (defaultInterpreterGroup == null) { defaultInterpreterGroup = zConf.getString( ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETER_GROUP_DEFAULT); } - if (StringUtils.isBlank(noteName)) { - noteName = "Untitled Note"; - } + try { - Note note = notebook.createNote(noteName, defaultInterpreterGroup, context.getAutheInfo()); - note.addNewParagraph(context.getAutheInfo()); // it's an empty note. so add one paragraph - note.setName(noteName); - note.setCronSupported(notebook.getConf()); - note.persist(context.getAutheInfo()); + Note note = notebook.createNote(normalizeNotePath(notePath), defaultInterpreterGroup, + context.getAutheInfo()); + // it's an empty note. so add one paragraph + note.addNewParagraph(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(note, context); return note; } catch (IOException e) { - callback.onFailure(new IOException("Fail to create Note", e), context); + callback.onFailure(new IOException("Fail to create note", e), context); return null; } } + String normalizeNotePath(String notePath) throws IOException { + if (StringUtils.isBlank(notePath)) { + notePath = "/Untitled Note"; + } + if (!notePath.startsWith("/")) { + notePath = "/" + notePath; + } + + notePath = notePath.replace("\r", " ").replace("\n", " "); + int pos = notePath.lastIndexOf("/"); + if ((notePath.length() - pos) > 255) { + throw new IOException("Note name must be less than 255"); + } + + if (notePath.contains("..")) { + throw new IOException("Note name can not contain '..'"); + } + return notePath; + } public void removeNote(String noteId, ServiceContext context, @@ -194,15 +235,10 @@ public class NotebookService { } } - public List<Map<String, String>> listNotes(boolean needsReload, - ServiceContext context, - ServiceCallback<List<Map<String, String>>> callback) + public List<NoteInfo> listNotesInfo(boolean needsReload, + ServiceContext context, + ServiceCallback<List<NoteInfo>> callback) throws IOException { - - ZeppelinConfiguration conf = notebook.getConf(); - String homeScreenNoteId = conf.getString(ZEPPELIN_NOTEBOOK_HOMESCREEN); - boolean hideHomeScreenNotebookFromList = - conf.getBoolean(ZeppelinConfiguration.ConfVars.ZEPPELIN_NOTEBOOK_HOMESCREEN_HIDE); if (needsReload) { try { notebook.reloadAllNotes(context.getAutheInfo()); @@ -210,36 +246,30 @@ public class NotebookService { LOGGER.error("Fail to reload notes from repository", e); } } - - List<Note> notes = notebook.getAllNotes(context.getUserAndRoles()); - 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); - } - + List<NoteInfo> notesInfo = notebook.getNotesInfo(context.getUserAndRoles()); callback.onSuccess(notesInfo, context); return notesInfo; } public void renameNote(String noteId, - String newNoteName, + String newNotePath, + boolean isRelative, ServiceContext context, ServiceCallback<Note> callback) throws IOException { - if (!checkPermission(noteId, Permission.WRITER, Message.OP.NOTE_RENAME, context, callback)) { + if (!checkPermission(noteId, Permission.OWNER, Message.OP.NOTE_RENAME, context, callback)) { return; } - Note note = notebook.getNote(noteId); if (note != null) { - note.setName(newNoteName); note.setCronSupported(notebook.getConf()); - note.persist(context.getAutheInfo()); + if (isRelative) { + newNotePath = note.getParentPath() + "/" + newNotePath; + } else { + if (!newNotePath.startsWith("/")) { + newNotePath = "/" + newNotePath; + } + } + notebook.moveNote(noteId, newNotePath, context.getAutheInfo()); callback.onSuccess(note, context); } else { callback.onFailure(new NoteNotFoundException(noteId), context); @@ -248,22 +278,39 @@ public class NotebookService { } public Note cloneNote(String noteId, - String newNoteName, + String newNotePath, ServiceContext context, ServiceCallback<Note> callback) throws IOException { - Note newNote = notebook.cloneNote(noteId, newNoteName, context.getAutheInfo()); - callback.onSuccess(newNote, context); - return newNote; + //TODO(zjffdu) move these to Notebook + if (StringUtils.isBlank(newNotePath)) { + newNotePath = "/Cloned Note_" + noteId; + } + try { + Note newNote = notebook.cloneNote(noteId, normalizeNotePath(newNotePath), + context.getAutheInfo()); + callback.onSuccess(newNote, context); + return newNote; + } catch (IOException e) { + callback.onFailure(new IOException("Fail to clone note", e), context); + return null; + } } - public Note importNote(String noteName, + public Note importNote(String notePath, String noteJson, ServiceContext context, ServiceCallback<Note> callback) throws IOException { - Note note = notebook.importNote(noteJson, noteName, context.getAutheInfo()); - note.persist(context.getAutheInfo()); - callback.onSuccess(note, context); - return note; + try { + // pass notePath when it is null + Note note = notebook.importNote(noteJson, notePath == null ? + notePath : normalizeNotePath(notePath), + context.getAutheInfo()); + callback.onSuccess(note, context); + return note; + } catch (IOException e) { + callback.onFailure(new IOException("Fail to import note: " + e.getMessage(), e), context); + return null; + } } public boolean runParagraph(String noteId, @@ -311,7 +358,7 @@ public class NotebookService { } try { - note.persist(p.getAuthenticationInfo()); + notebook.saveNote(note, context.getAutheInfo()); boolean result = note.run(p.getId(), blocking); callback.onSuccess(p, context); return result; @@ -319,7 +366,8 @@ public class NotebookService { LOGGER.error("Exception from run", ex); p.setReturn(new InterpreterResult(InterpreterResult.Code.ERROR, ex.getMessage()), ex); p.setStatus(Job.Status.ERROR); - callback.onFailure(new Exception("Fail to run paragraph " + paragraphId, ex), context); + // don't call callback.onFailure, we just need to display the error message + // in paragraph result section instead of pop up the error window. return false; } } @@ -399,7 +447,7 @@ public class NotebookService { return; } note.moveParagraph(paragraphId, newIndex); - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(note.getParagraph(newIndex), context); } @@ -419,7 +467,7 @@ public class NotebookService { throw new ParagraphNotFoundException(paragraphId); } Paragraph p = note.removeParagraph(context.getAutheInfo().getUser(), paragraphId); - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(p, context); } @@ -438,7 +486,7 @@ public class NotebookService { } Paragraph newPara = note.insertNewParagraph(index, context.getAutheInfo()); newPara.setConfig(config); - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(newPara, context); return newPara; } @@ -455,21 +503,54 @@ public class NotebookService { callback.onFailure(new NoteNotFoundException(noteId), context); return; } - //restore cron - Map<String, Object> config = note.getConfig(); - if (config.get("cron") != null) { - notebook.refreshCron(note.getId()); + + if (!note.getPath().startsWith("/" + NoteManager.TRASH_FOLDER)) { + callback.onFailure(new IOException("Can not restore this note " + note.getPath() + + " as it is not in trash folder"), context); + return; + } + try { + String destNotePath = note.getPath().replace("/" + NoteManager.TRASH_FOLDER, ""); + notebook.moveNote(noteId, destNotePath, context.getAutheInfo()); + callback.onSuccess(note, context); + } catch (IOException e) { + callback.onFailure(new IOException("Fail to restore note: " + noteId, e), context); } - if (note.isTrash()) { - String newName = note.getName().replaceFirst(Folder.TRASH_FOLDER_ID + "/", ""); - renameNote(noteId, newName, context, callback); - } else { - callback.onFailure(new IOException(String.format("Trying to restore a note {} " + - "which is not in Trash", noteId)), context); + } + + public void restoreFolder(String folderPath, + ServiceContext context, + ServiceCallback<Void> callback) throws IOException { + + if (!folderPath.startsWith("/" + NoteManager.TRASH_FOLDER)) { + callback.onFailure(new IOException("Can not restore this folder: " + folderPath + + " as it is not in trash folder"), context); + return; + } + try { + String destFolderPath = folderPath.replace("/" + NoteManager.TRASH_FOLDER, ""); + notebook.moveFolder(folderPath, destFolderPath, context.getAutheInfo()); + callback.onSuccess(null, context); + } catch (IOException e) { + callback.onFailure(new IOException("Fail to restore folder: " + folderPath, e), context); + } + + } + + + public void restoreAll(ServiceContext context, + ServiceCallback callback) throws IOException { + + try { + notebook.restoreAll(context.getAutheInfo()); + callback.onSuccess(null, context); + } catch (IOException e) { + callback.onFailure(new IOException("Fail to restore all", e), context); } } + public void updateParagraph(String noteId, String paragraphId, String title, @@ -504,7 +585,7 @@ public class NotebookService { p.setTitle(title); p.setText(text); } - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(p, context); } @@ -555,7 +636,6 @@ public class NotebookService { } - public void updateNote(String noteId, String name, Map<String, Object> config, @@ -584,7 +664,7 @@ public class NotebookService { notebook.refreshCron(note.getId()); } - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(note, context); } @@ -619,7 +699,7 @@ public class NotebookService { } note.setNoteParams(noteParams); - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(note, context); } @@ -640,7 +720,7 @@ public class NotebookService { note.getNoteForms().remove(formName); note.getNoteParams().remove(formName); - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); callback.onSuccess(note, context); } @@ -662,7 +742,7 @@ public class NotebookService { } NotebookRepoWithVersionControl.Revision revision = - notebook.checkpointNote(noteId, commitMessage, context.getAutheInfo()); + notebook.checkpointNote(noteId, note.getName(), commitMessage, context.getAutheInfo()); callback.onSuccess(revision, context); return revision; } @@ -685,7 +765,7 @@ public class NotebookService { // return null; // } List<NotebookRepoWithVersionControl.Revision> revisions = - notebook.listRevisionHistory(noteId, context.getAutheInfo()); + notebook.listRevisionHistory(noteId, note.getPath(), context.getAutheInfo()); callback.onSuccess(revisions, context); return revisions; } @@ -707,7 +787,8 @@ public class NotebookService { } try { - Note resultNote = notebook.setNoteRevision(noteId, revisionId, context.getAutheInfo()); + Note resultNote = notebook.setNoteRevision(noteId, note.getPath(), revisionId, + context.getAutheInfo()); callback.onSuccess(resultNote, context); return resultNote; } catch (Exception e) { @@ -731,7 +812,8 @@ public class NotebookService { callback)) { return; } - Note revisionNote = notebook.getNoteByRevision(noteId, revisionId, context.getAutheInfo()); + Note revisionNote = notebook.getNoteByRevision(noteId, note.getPath(), revisionId, + context.getAutheInfo()); callback.onSuccess(revisionNote, context); } @@ -754,7 +836,8 @@ public class NotebookService { if (revisionId.equals("Head")) { revisionNote = notebook.getNote(noteId); } else { - revisionNote = notebook.getNoteByRevision(noteId, revisionId, context.getAutheInfo()); + revisionNote = notebook.getNoteByRevision(noteId, note.getPath(), revisionId, + context.getAutheInfo()); } callback.onSuccess(revisionNote, context); } @@ -792,7 +875,6 @@ public class NotebookService { String replName, ServiceContext context, ServiceCallback<Map<String, Object>> callback) throws IOException { - Note note = notebook.getNote(noteId); if (note == null) { callback.onFailure(new NoteNotFoundException(noteId), context); @@ -828,10 +910,276 @@ public class NotebookService { } note.setPersonalizedMode(isPersonalized); - note.persist(context.getAutheInfo()); + notebook.saveNote(note, context.getAutheInfo()); + callback.onSuccess(note, context); + } + + public void moveNoteToTrash(String noteId, + ServiceContext context, + ServiceCallback<Note> callback) throws IOException { + Note note = notebook.getNote(noteId); + if (note == null) { + callback.onFailure(new NoteNotFoundException(noteId), context); + return; + } + + if (!checkPermission(noteId, Permission.OWNER, Message.OP.MOVE_NOTE_TO_TRASH, context, + callback)) { + return; + } + String destNotePath = "/" + NoteManager.TRASH_FOLDER + note.getPath(); + if (notebook.containsNote(destNotePath)) { + destNotePath = destNotePath + " " + TRASH_CONFLICT_TIMESTAMP_FORMATTER.print(new DateTime()); + } + notebook.moveNote(noteId, destNotePath, context.getAutheInfo()); callback.onSuccess(note, context); } + public void moveFolderToTrash(String folderPath, + ServiceContext context, + ServiceCallback<Void> callback) throws IOException { + + //TODO(zjffdu) folder permission check + //TODO(zjffdu) folderPath is relative path, need to fix it in frontend + LOGGER.info("Move folder " + folderPath + " to trash"); + + String destFolderPath = "/" + NoteManager.TRASH_FOLDER + "/" + folderPath; + if (notebook.containsNote(destFolderPath)) { + destFolderPath = destFolderPath + " " + + TRASH_CONFLICT_TIMESTAMP_FORMATTER.print(new DateTime()); + } + + notebook.moveFolder("/" + folderPath, destFolderPath, context.getAutheInfo()); + callback.onSuccess(null, context); + } + + public void emptyTrash(ServiceContext context, + ServiceCallback<Void> callback) throws IOException { + + try { + notebook.emptyTrash(context.getAutheInfo()); + callback.onSuccess(null, context); + } catch (IOException e) { + callback.onFailure(e, context); + } + + } + + public List<NoteInfo> removeFolder(String folderPath, + ServiceContext context, + ServiceCallback<List<NoteInfo>> callback) throws IOException { + try { + notebook.removeFolder(folderPath, context.getAutheInfo()); + List<NoteInfo> notesInfo = notebook.getNotesInfo(context.getUserAndRoles()); + callback.onSuccess(notesInfo, context); + return notesInfo; + } catch (IOException e) { + callback.onFailure(e, context); + return null; + } + } + + public List<NoteInfo> renameFolder(String folderPath, + String newFolderPath, + ServiceContext context, + ServiceCallback<List<NoteInfo>> callback) throws IOException { + //TODO(zjffdu) folder permission check + + try { + notebook.moveFolder(folderPath, newFolderPath, context.getAutheInfo()); + List<NoteInfo> notesInfo = notebook.getNotesInfo(context.getUserAndRoles()); + callback.onSuccess(notesInfo, context); + return notesInfo; + } catch (IOException e) { + callback.onFailure(e, context); + return null; + } + } + + public void spell(String noteId, + Message message, + ServiceContext context, + ServiceCallback<Paragraph> callback) throws IOException { + + try { + if (!checkPermission(noteId, Permission.RUNNER, Message.OP.RUN_PARAGRAPH_USING_SPELL, context, + callback)) { + return; + } + + String paragraphId = (String) message.get("id"); + if (paragraphId == null) { + return; + } + + String text = (String) message.get("paragraph"); + String title = (String) message.get("title"); + Job.Status status = Job.Status.valueOf((String) message.get("status")); + Map<String, Object> params = (Map<String, Object>) message.get("params"); + Map<String, Object> config = (Map<String, Object>) message.get("config"); + + Note note = notebook.getNote(noteId); + Paragraph p = setParagraphUsingMessage(note, message, paragraphId, + text, title, params, config); + p.setResult((InterpreterResult) message.get("results")); + p.setErrorMessage((String) message.get("errorMessage")); + p.setStatusWithoutNotification(status); + + // Spell uses ISO 8601 formatted string generated from moment + String dateStarted = (String) message.get("dateStarted"); + String dateFinished = (String) message.get("dateFinished"); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"); + + try { + p.setDateStarted(df.parse(dateStarted)); + } catch (ParseException e) { + LOGGER.error("Failed parse dateStarted", e); + } + + try { + p.setDateFinished(df.parse(dateFinished)); + } catch (ParseException e) { + LOGGER.error("Failed parse dateFinished", e); + } + + addNewParagraphIfLastParagraphIsExecuted(note, p); + notebook.saveNote(note, context.getAutheInfo()); + callback.onSuccess(p, context); + } catch (IOException e) { + callback.onFailure(new IOException("Fail to run spell", e), context); + } + + } + + 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()) || + Strings.isNullOrEmpty(p.getScriptText())) && + isTheLastParagraph) { + note.addNewParagraph(p.getAuthenticationInfo()); + } + } + + + 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.roles, 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; + } + + public void updateAngularObject(String noteId, String paragraphId, String interpreterGroupId, + String varName, Object varValue, + ServiceContext context, + ServiceCallback<AngularObject> callback) throws IOException { + + String user = context.getAutheInfo().getUser(); + 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) { + LOGGER.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; + } + } + } + + callback.onSuccess(ao, context); + } + + public void patchParagraph(final String noteId, final String paragraphId, String patchText, + ServiceContext context, + ServiceCallback<String> callback) throws IOException { + + try { + if (!checkPermission(noteId, Permission.WRITER, Message.OP.PATCH_PARAGRAPH, context, + callback)) { + return; + } + + + Note note = notebook.getNote(noteId); + if (note == null) { + return; + } + Paragraph p = note.getParagraph(paragraphId); + if (p == null) { + return; + } + + DiffMatchPatch dmp = new DiffMatchPatch(); + LinkedList<DiffMatchPatch.Patch> patches = null; + try { + patches = (LinkedList<DiffMatchPatch.Patch>) dmp.patchFromText(patchText); + } catch (ClassCastException e) { + LOGGER.error("Failed to parse patches", e); + } + if (patches == null) { + return; + } + + String paragraphText = p.getText() == null ? "" : p.getText(); + paragraphText = (String) dmp.patchApply(patches, paragraphText)[0]; + p.setText(paragraphText); + callback.onSuccess(paragraphText, context); + } catch (IOException e) { + callback.onFailure(new IOException("Fail to patch", e), context); + } + } + enum Permission { READER,
http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-server/src/main/java/org/apache/zeppelin/service/SimpleServiceCallback.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/service/SimpleServiceCallback.java b/zeppelin-server/src/main/java/org/apache/zeppelin/service/SimpleServiceCallback.java index 6957707..bf6616a 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/service/SimpleServiceCallback.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/service/SimpleServiceCallback.java @@ -43,7 +43,11 @@ public class SimpleServiceCallback<T> implements ServiceCallback<T> { @Override public void onFailure(Exception ex, ServiceContext context) throws IOException { - LOGGER.warn(ex.getMessage()); + String message = ex.getMessage(); + if (ex.getCause() != null) { + message += ", cause: " + ex.getCause().getMessage(); + } + LOGGER.warn(message); } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-server/src/main/java/org/apache/zeppelin/socket/ConnectionManager.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/ConnectionManager.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/ConnectionManager.java index ffadfa0..e2b3b38 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/ConnectionManager.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/ConnectionManager.java @@ -27,6 +27,7 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration; import org.apache.zeppelin.display.GUI; import org.apache.zeppelin.display.Input; import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.NoteInfo; import org.apache.zeppelin.notebook.NotebookAuthorization; import org.apache.zeppelin.notebook.NotebookImportDeserializer; import org.apache.zeppelin.notebook.Paragraph; @@ -350,7 +351,7 @@ public class ConnectionManager { } } - public void broadcastNoteListExcept(List<Map<String, String>> notesInfo, + public void broadcastNoteListExcept(List<NoteInfo> notesInfo, AuthenticationInfo subject) { Set<String> userAndRoles; NotebookAuthorization authInfo = NotebookAuthorization.getInstance(); http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java index 64e70e9..6d5cdb2 100644 --- a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java +++ b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java @@ -23,7 +23,6 @@ import com.google.gson.reflect.TypeToken; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; 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; @@ -38,11 +37,11 @@ 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.notebook.Folder; import org.apache.zeppelin.notebook.Note; +import org.apache.zeppelin.notebook.NoteEventListener; +import org.apache.zeppelin.notebook.NoteInfo; import org.apache.zeppelin.notebook.Notebook; import org.apache.zeppelin.notebook.NotebookAuthorization; -import org.apache.zeppelin.notebook.NotebookEventListener; import org.apache.zeppelin.notebook.NotebookImportDeserializer; import org.apache.zeppelin.notebook.Paragraph; import org.apache.zeppelin.notebook.ParagraphJobListener; @@ -63,13 +62,8 @@ import org.apache.zeppelin.types.InterpreterSettingsList; import org.apache.zeppelin.user.AuthenticationInfo; import org.apache.zeppelin.utils.InterpreterBindingUtils; import org.apache.zeppelin.utils.SecurityUtils; -import org.bitbucket.cowwoc.diffmatchpatch.DiffMatchPatch; 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; @@ -78,21 +72,16 @@ import java.io.IOException; import java.lang.reflect.Type; import java.net.URISyntaxException; import java.net.UnknownHostException; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Zeppelin websocket service. @@ -103,6 +92,7 @@ public class NotebookServer extends WebSocketServlet RemoteInterpreterProcessListener, ApplicationEventListener, ParagraphJobListener, + NoteEventListener, NotebookServerMBean { /** @@ -122,7 +112,6 @@ public class NotebookServer extends WebSocketServlet } - // private HashSet<String> collaborativeModeList = new HashSet<>(); private Boolean collaborativeModeEnable = ZeppelinConfiguration .create() .isZeppelinNotebookCollaborativeModeEnable(); @@ -251,27 +240,14 @@ public class NotebookServer extends WebSocketServlet 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())) { connectionManager.addUserConnection(messagereceived.principal, conn); } - AuthenticationInfo subject = - new AuthenticationInfo(messagereceived.principal, messagereceived.roles, - messagereceived.ticket); // Lets be elegant here switch (messagereceived.op) { case LIST_NOTES: - listNotes(conn, messagereceived); + listNotesInfo(conn, messagereceived); break; case RELOAD_NOTES_FROM_REPO: broadcastReloadedNoteList(conn, getServiceContext(messagereceived)); @@ -289,25 +265,25 @@ public class NotebookServer extends WebSocketServlet deleteNote(conn, messagereceived); break; case REMOVE_FOLDER: - removeFolder(conn, userAndRoles, notebook, messagereceived); + removeFolder(conn, messagereceived); break; case MOVE_NOTE_TO_TRASH: - moveNoteToTrash(conn, userAndRoles, notebook, messagereceived); + moveNoteToTrash(conn, messagereceived); break; case MOVE_FOLDER_TO_TRASH: - moveFolderToTrash(conn, userAndRoles, notebook, messagereceived); + moveFolderToTrash(conn, messagereceived); break; case EMPTY_TRASH: - emptyTrash(conn, userAndRoles, notebook, messagereceived); + emptyTrash(conn, messagereceived); break; case RESTORE_FOLDER: - restoreFolder(conn, userAndRoles, notebook, messagereceived); + restoreFolder(conn, messagereceived); break; case RESTORE_NOTE: restoreNote(conn, messagereceived); break; case RESTORE_ALL: - restoreAll(conn, userAndRoles, notebook, messagereceived); + restoreAll(conn, messagereceived); break; case CLONE_NOTE: cloneNote(conn, messagereceived); @@ -322,7 +298,7 @@ public class NotebookServer extends WebSocketServlet runParagraph(conn, messagereceived); break; case PARAGRAPH_EXECUTED_BY_SPELL: - broadcastSpellExecution(conn, userAndRoles, notebook, messagereceived); + broadcastSpellExecution(conn, messagereceived); break; case RUN_ALL_PARAGRAPHS: runAllParagraphs(conn, messagereceived); @@ -355,10 +331,10 @@ public class NotebookServer extends WebSocketServlet renameNote(conn, messagereceived); break; case FOLDER_RENAME: - renameFolder(conn, userAndRoles, notebook, messagereceived); + renameFolder(conn, messagereceived); break; case UPDATE_PERSONALIZED_MODE: - updatePersonalizedMode(conn, userAndRoles, notebook, messagereceived); + updatePersonalizedMode(conn, messagereceived); break; case COMPLETION: completion(conn, messagereceived); @@ -366,13 +342,13 @@ public class NotebookServer extends WebSocketServlet case PING: break; //do nothing case ANGULAR_OBJECT_UPDATED: - angularObjectUpdated(conn, userAndRoles, notebook, messagereceived); + angularObjectUpdated(conn, messagereceived); break; case ANGULAR_OBJECT_CLIENT_BIND: - angularObjectClientBind(conn, userAndRoles, notebook, messagereceived); + angularObjectClientBind(conn, messagereceived); break; case ANGULAR_OBJECT_CLIENT_UNBIND: - angularObjectClientUnbind(conn, userAndRoles, notebook, messagereceived); + angularObjectClientUnbind(conn, messagereceived); break; case LIST_CONFIGURATIONS: sendAllConfigurations(conn, messagereceived); @@ -417,7 +393,7 @@ public class NotebookServer extends WebSocketServlet removeNoteForms(conn, messagereceived); break; case PATCH_PARAGRAPH: - patchParagraph(conn, userAndRoles, notebook, messagereceived); + patchParagraph(conn, messagereceived); break; default: break; @@ -506,41 +482,6 @@ public class NotebookServer extends WebSocketServlet 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) { connectionManager.broadcast(note.getId(), new Message(OP.NOTE).put("note", note)); } @@ -578,18 +519,18 @@ public class NotebookServer extends WebSocketServlet subject = new AuthenticationInfo(StringUtils.EMPTY); } //send first to requesting user - List<Map<String, String>> notesInfo = generateNotesInfo(false, subject, userAndRoles); + List<NoteInfo> notesInfo = notebook().getNotesInfo(userAndRoles); connectionManager.multicastToUser(subject.getUser(), new Message(OP.NOTES_INFO).put("notes", notesInfo)); //to others afterwards connectionManager.broadcastNoteListExcept(notesInfo, subject); } - public void listNotes(NotebookSocket conn, Message message) throws IOException { - getNotebookService().listNotes(false, getServiceContext(message), - new WebSocketServiceCallback<List<Map<String, String>>>(conn) { + public void listNotesInfo(NotebookSocket conn, Message message) throws IOException { + getNotebookService().listNotesInfo(false, getServiceContext(message), + new WebSocketServiceCallback<List<NoteInfo>>(conn) { @Override - public void onSuccess(List<Map<String, String>> notesInfo, + public void onSuccess(List<NoteInfo> notesInfo, ServiceContext context) throws IOException { super.onSuccess(notesInfo, context); connectionManager.unicast(new Message(OP.NOTES_INFO).put("notes", notesInfo), conn); @@ -599,10 +540,10 @@ public class NotebookServer extends WebSocketServlet public void broadcastReloadedNoteList(NotebookSocket conn, ServiceContext context) throws IOException { - getNotebookService().listNotes(false, context, - new WebSocketServiceCallback<List<Map<String, String>>>(conn) { + getNotebookService().listNotesInfo(false, context, + new WebSocketServiceCallback<List<NoteInfo>>(conn) { @Override - public void onSuccess(List<Map<String, String>> notesInfo, + public void onSuccess(List<NoteInfo> notesInfo, ServiceContext context) throws IOException { super.onSuccess(notesInfo, context); connectionManager.multicastToUser(context.getAutheInfo().getUser(), @@ -628,7 +569,7 @@ public class NotebookServer extends WebSocketServlet * @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 noteId, Set<String> userAndRoles, String principal, String op) throws IOException { NotebookAuthorization notebookAuthorization = notebook.getNotebookAuthorization(); @@ -641,22 +582,6 @@ public class NotebookServer extends WebSocketServlet 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 getNote(NotebookSocket conn, Message fromMessage) throws IOException { String noteId = (String) fromMessage.get("id"); @@ -718,8 +643,8 @@ public class NotebookServer extends WebSocketServlet }); } - private void updatePersonalizedMode(NotebookSocket conn, HashSet<String> userAndRoles, - Notebook notebook, Message fromMessage) throws IOException { + private void updatePersonalizedMode(NotebookSocket conn, + Message fromMessage) throws IOException { String noteId = (String) fromMessage.get("id"); String personalized = (String) fromMessage.get("personalized"); boolean isPersonalized = personalized.equals("true") ? true : false; @@ -738,10 +663,14 @@ public class NotebookServer extends WebSocketServlet Message fromMessage) throws IOException { String noteId = (String) fromMessage.get("id"); String name = (String) fromMessage.get("name"); + boolean isRelativePath = false; + if (fromMessage.get("relative") != null) { + isRelativePath = (boolean) fromMessage.get("relative"); + } if (noteId == null) { return; } - getNotebookService().renameNote(noteId, name, getServiceContext(fromMessage), + getNotebookService().renameNote(noteId, name, isRelativePath, getServiceContext(fromMessage), new WebSocketServiceCallback<Note>(conn) { @Override public void onSuccess(Note note, ServiceContext context) throws IOException { @@ -752,41 +681,18 @@ public class NotebookServer extends WebSocketServlet }); } - private void renameFolder(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook, + private void renameFolder(NotebookSocket conn, Message fromMessage) throws IOException { - renameFolder(conn, userAndRoles, notebook, fromMessage, "rename"); - } - - private void renameFolder(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook, - Message fromMessage, String op) throws 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); - } + getNotebookService().renameFolder(oldFolderId, newFolderId, getServiceContext(fromMessage), + new WebSocketServiceCallback<List<NoteInfo>>(conn) { + @Override + public void onSuccess(List<NoteInfo> result, ServiceContext context) throws IOException { + super.onSuccess(result, context); + broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles()); + } + }); } private void createNote(NotebookSocket conn, @@ -828,138 +734,100 @@ public class NotebookServer extends WebSocketServlet }); } - private void removeFolder(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook, + private void removeFolder(NotebookSocket conn, Message fromMessage) throws IOException { - String folderId = (String) fromMessage.get("id"); - if (folderId == null) { - return; - } - - List<Note> notes = notebook.getNotesUnderFolder(folderId, userAndRoles); - 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); - connectionManager.removeNoteConnection(note.getId()); - } - broadcastNoteList(subject, userAndRoles); + String folderPath = (String) fromMessage.get("id"); + folderPath = "/" + folderPath; + getNotebookService().removeFolder(folderPath, getServiceContext(fromMessage), + new WebSocketServiceCallback<List<NoteInfo>>(conn) { + @Override + public void onSuccess(List<NoteInfo> notesInfo, + ServiceContext context) throws IOException { + super.onSuccess(notesInfo, context); + for (NoteInfo noteInfo : notesInfo) { + connectionManager.removeNoteConnection(noteInfo.getId()); + } + broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles()); + } + }); } - private void moveNoteToTrash(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook, - Message fromMessage) throws SchedulerException, IOException { + private void moveNoteToTrash(NotebookSocket conn, + Message fromMessage) throws IOException { String noteId = (String) fromMessage.get("id"); - if (noteId == null) { - return; - } - - Note note = notebook.getNote(noteId); - - // drop cron - Map<String, Object> config = note.getConfig(); - if (config.get("cron") != null) { - notebook.removeCron(note.getId()); - } - - if (note != null && !note.isTrash()) { - fromMessage.put("name", Folder.TRASH_FOLDER_ID + "/" + note.getName()); - renameNote(conn, fromMessage); - notebook.moveNoteToTrash(note.getId()); - } + getNotebookService().moveNoteToTrash(noteId, getServiceContext(fromMessage), + new WebSocketServiceCallback<Note>(conn) { + @Override + public void onSuccess(Note note, ServiceContext context) throws IOException { + super.onSuccess(note, context); + broadcastNote(note); + broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles()); + } + }); } - 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); - } - - List<Note> noteList = folder.getNotesRecursively(); - for (Note note : noteList) { - Map<String, Object> config = note.getConfig(); - if (config.get("cron") != null) { - notebook.removeCron(note.getId()); - } - } + private void moveFolderToTrash(NotebookSocket conn, + Message fromMessage) + throws IOException { - fromMessage.put("name", trashFolderId); - renameFolder(conn, userAndRoles, notebook, fromMessage, "move"); - } + String folderPath = (String) fromMessage.get("id"); + getNotebookService().moveFolderToTrash(folderPath, getServiceContext(fromMessage), + new WebSocketServiceCallback<Void>(conn) { + @Override + public void onSuccess(Void result, ServiceContext context) throws IOException { + super.onSuccess(result, context); + broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles()); + } + }); + } private void restoreNote(NotebookSocket conn, Message fromMessage) throws IOException { String noteId = (String) fromMessage.get("id"); getNotebookService().restoreNote(noteId, getServiceContext(fromMessage), - new WebSocketServiceCallback(conn)); + new WebSocketServiceCallback<Note>(conn) { + @Override + public void onSuccess(Note note, ServiceContext context) throws IOException { + super.onSuccess(note, context); + broadcastNote(note); + broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles()); + } + }); } - private void restoreFolder(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook, + private void restoreFolder(NotebookSocket conn, Message fromMessage) throws 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(); - - //restore cron for each paragraph - List<Note> noteList = folder.getNotesRecursively(); - for (Note note : noteList) { - Map<String, Object> config = note.getConfig(); - if (config.get("cron") != null) { - notebook.refreshCron(note.getId()); - } - } - - // 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"); - } + String folderPath = (String) fromMessage.get("id"); + folderPath = "/" + folderPath; + getNotebookService().restoreFolder(folderPath, getServiceContext(fromMessage), + new WebSocketServiceCallback(conn) { + @Override + public void onSuccess(Object result, ServiceContext context) throws IOException { + super.onSuccess(result, context); + broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles()); + } + }); } - private void restoreAll(NotebookSocket conn, HashSet<String> userAndRoles, Notebook notebook, + private void restoreAll(NotebookSocket conn, Message fromMessage) throws 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"); - } + getNotebookService().restoreAll(getServiceContext(fromMessage), + new WebSocketServiceCallback(conn) { + @Override + public void onSuccess(Object result, ServiceContext context) throws IOException { + super.onSuccess(result, context); + broadcastNoteList(context.getAutheInfo(), context.getUserAndRoles()); + } + }); } - 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 emptyTrash(NotebookSocket conn, + Message fromMessage) throws IOException { + getNotebookService().emptyTrash(getServiceContext(fromMessage), + new WebSocketServiceCallback(conn)); } private void updateParagraph(NotebookSocket conn, @@ -991,8 +859,8 @@ public class NotebookServer extends WebSocketServlet }); } - private void patchParagraph(NotebookSocket conn, HashSet<String> userAndRoles, - Notebook notebook, Message fromMessage) throws IOException { + private void patchParagraph(NotebookSocket conn, + Message fromMessage) throws IOException { if (!collaborativeModeEnable) { return; } @@ -1008,43 +876,23 @@ public class NotebookServer extends WebSocketServlet return; } } - - if (!hasParagraphWriterPermission(conn, notebook, noteId, - userAndRoles, fromMessage.principal, "write")) { - return; - } - - final Note note = notebook.getNote(noteId); - if (note == null) { - return; - } - Paragraph p = note.getParagraph(paragraphId); - if (p == null) { - return; - } - - DiffMatchPatch dmp = new DiffMatchPatch(); + final String noteId2 = noteId; String patchText = fromMessage.getType("patch", LOG); if (patchText == null) { return; } - LinkedList<DiffMatchPatch.Patch> patches = null; - try { - patches = (LinkedList<DiffMatchPatch.Patch>) dmp.patchFromText(patchText); - } catch (ClassCastException e) { - LOG.error("Failed to parse patches", e); - } - if (patches == null) { - return; - } - - String paragraphText = p.getText() == null ? "" : p.getText(); - paragraphText = (String) dmp.patchApply(patches, paragraphText)[0]; - p.setText(paragraphText); - Message message = new Message(OP.PATCH_PARAGRAPH).put("patch", patchText) - .put("paragraphId", p.getId()); - connectionManager.broadcastExcept(note.getId(), message, conn); + getNotebookService().patchParagraph(noteId, paragraphId, patchText, + getServiceContext(fromMessage), + new WebSocketServiceCallback<String>(conn) { + @Override + public void onSuccess(String result, ServiceContext context) throws IOException { + super.onSuccess(result, context); + Message message = new Message(OP.PATCH_PARAGRAPH).put("patch", result) + .put("paragraphId", paragraphId); + connectionManager.broadcastExcept(noteId2, message, conn); + } + }); } private void cloneNote(NotebookSocket conn, @@ -1102,7 +950,7 @@ public class NotebookServer extends WebSocketServlet final String paragraphId = (String) fromMessage.get("id"); String noteId = connectionManager.getAssociatedNoteId(conn); getNotebookService().removeParagraph(noteId, paragraphId, - getServiceContext(fromMessage), new WebSocketServiceCallback<Paragraph>(conn){ + getServiceContext(fromMessage), new WebSocketServiceCallback<Paragraph>(conn) { @Override public void onSuccess(Paragraph p, ServiceContext context) throws IOException { super.onSuccess(p, context); @@ -1162,101 +1010,42 @@ public class NotebookServer extends WebSocketServlet * 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) { + private void angularObjectUpdated(NotebookSocket conn, + Message fromMessage) throws IOException { 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(); - connectionManager.broadcastExcept(n.getId(), + getNotebookService().updateAngularObject(noteId, paragraphId, interpreterGroupId, + varName, varValue, getServiceContext(fromMessage), + new WebSocketServiceCallback<AngularObject>(conn) { + @Override + public void onSuccess(AngularObject ao, ServiceContext context) throws IOException { + super.onSuccess(ao, context); + connectionManager.broadcastExcept(noteId, new Message(OP.ANGULAR_OBJECT_UPDATE).put("angularObject", ao) - .put("interpreterGroupId", interpreterGroupId).put("noteId", n.getId()) + .put("interpreterGroupId", interpreterGroupId).put("noteId", noteId) .put("paragraphId", ao.getParagraphId()), conn); } - } - } - } else { // broadcast to all web session for the note - connectionManager.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 { + protected void angularObjectClientBind(NotebookSocket conn, + 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); + Note note = notebook().getNote(noteId); if (paragraphId == null) { throw new IllegalArgumentException( @@ -1265,18 +1054,10 @@ public class NotebookServer extends WebSocketServlet 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); - } + final RemoteAngularObjectRegistry registry = (RemoteAngularObjectRegistry) + interpreterGroup.getAngularObjectRegistry(); + pushAngularObjectToRemoteRegistry(noteId, paragraphId, varName, varValue, registry, + interpreterGroup.getId(), conn); } } @@ -1284,13 +1065,13 @@ public class NotebookServer extends WebSocketServlet * 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) + protected void angularObjectClientUnbind(NotebookSocket conn, + Message fromMessage) throws Exception { String noteId = fromMessage.getType("noteId"); String varName = fromMessage.getType("name"); String paragraphId = fromMessage.getType("paragraphId"); - Note note = notebook.getNote(noteId); + Note note = notebook().getNote(noteId); if (paragraphId == null) { throw new IllegalArgumentException( @@ -1299,17 +1080,11 @@ public class NotebookServer extends WebSocketServlet if (note != null) { final InterpreterGroup interpreterGroup = findInterpreterGroupForParagraph(note, paragraphId); + final RemoteAngularObjectRegistry registry = (RemoteAngularObjectRegistry) + interpreterGroup.getAngularObjectRegistry(); + removeAngularFromRemoteRegistry(noteId, paragraphId, varName, registry, + interpreterGroup.getId(), conn); - 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); - } } } @@ -1348,35 +1123,6 @@ public class NotebookServer extends WebSocketServlet .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); - } - - connectionManager.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) { - connectionManager.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, Message fromMessage) throws IOException { final String paragraphId = (String) fromMessage.get("id"); @@ -1449,58 +1195,20 @@ public class NotebookServer extends WebSocketServlet new WebSocketServiceCallback<Paragraph>(conn)); } - 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; - } + private void broadcastSpellExecution(NotebookSocket conn, + Message fromMessage) throws IOException { String noteId = connectionManager.getAssociatedNoteId(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((InterpreterResult) fromMessage.get("results")); - p.setErrorMessage((String) fromMessage.get("errorMessage")); - p.setStatusWithoutNotification(status); - - // Spell uses ISO 8601 formatted string generated from moment - String dateStarted = (String) fromMessage.get("dateStarted"); - String dateFinished = (String) fromMessage.get("dateFinished"); - SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX"); - - try { - p.setDateStarted(df.parse(dateStarted)); - } catch (ParseException e) { - LOG.error("Failed parse dateStarted", e); - } - - try { - p.setDateFinished(df.parse(dateFinished)); - } catch (ParseException e) { - LOG.error("Failed parse dateFinished", e); - } - - addNewParagraphIfLastParagraphIsExecuted(note, p); - if (!persistNoteWithAuthInfo(conn, note, p)) { - return; - } - - // broadcast to other clients only - connectionManager.broadcastExcept(note.getId(), - new Message(OP.RUN_PARAGRAPH_USING_SPELL).put("paragraph", p), conn); + getNotebookService().spell(noteId, fromMessage, + getServiceContext(fromMessage), new WebSocketServiceCallback<Paragraph>(conn) { + @Override + public void onSuccess(Paragraph p, ServiceContext context) throws IOException { + super.onSuccess(p, context); + // broadcast to other clients only + connectionManager.broadcastExcept(p.getNote().getId(), + new Message(OP.RUN_PARAGRAPH_USING_SPELL).put("paragraph", p), conn); + } + }); } private void runParagraph(NotebookSocket conn, @@ -1535,61 +1243,8 @@ public class NotebookServer extends WebSocketServlet }); } - 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()) || - Strings.isNullOrEmpty(p.getScriptText())) && - 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 (IOException 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 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.roles, 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, - Message message ) throws IOException { + Message message) throws IOException { getConfigurationService().getAllProperties(getServiceContext(message), new WebSocketServiceCallback<Map<String, String>>(conn) { @@ -1616,7 +1271,8 @@ public class NotebookServer extends WebSocketServlet super.onSuccess(revision, context); if (!Revision.isEmpty(revision)) { List<Revision> revisions = - notebook().listRevisionHistory(noteId, context.getAutheInfo()); + notebook().listRevisionHistory(noteId, notebook().getNote(noteId).getPath(), + context.getAutheInfo()); conn.send( serializeMessage(new Message(OP.LIST_REVISION_HISTORY).put("revisionList", revisions))); @@ -1717,6 +1373,7 @@ public class NotebookServer extends WebSocketServlet 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) { @@ -1734,6 +1391,7 @@ public class NotebookServer extends WebSocketServlet 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); @@ -1833,88 +1491,89 @@ public class NotebookServer extends WebSocketServlet } - /** - * Notebook Information Change event. - */ - public class NotebookInformationListener implements NotebookEventListener { - private NotebookServer notebookServer; - - public NotebookInformationListener(NotebookServer notebookServer) { - this.notebookServer = notebookServer; + @Override + public void onParagraphRemove(Paragraph p) { + try { + getJobManagerService().getNoteJobInfoByUnixTime(System.currentTimeMillis() - 5000, null, + new JobManagerServiceCallback()); + } catch (IOException e) { + LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); } + } - @Override - public void onParagraphRemove(Paragraph p) { - try { - getJobManagerService().getNoteJobInfoByUnixTime(System.currentTimeMillis() - 5000, null, - new JobManagerServiceCallback()); - } catch (IOException e) { - LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); - } + @Override + public void onNoteRemove(Note note, AuthenticationInfo subject) { + try { + broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000); + } catch (IOException e) { + LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); } - @Override - public void onNoteRemove(Note note) { - try { - notebookServer.broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000); - } catch (IOException e) { - LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); - } + try { + getJobManagerService().removeNoteJobInfo(note.getId(), null, + new JobManagerServiceCallback()); + } catch (IOException e) { + LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); + } - try { - getJobManagerService().removeNoteJobInfo(note.getId(), null, - new JobManagerServiceCallback()); - } catch (IOException e) { - LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); - } + } + @Override + public void onParagraphCreate(Paragraph p) { + try { + getJobManagerService().getNoteJobInfo(p.getNote().getId(), null, + new JobManagerServiceCallback()); + } catch (IOException e) { + LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); } + } - @Override - public void onParagraphCreate(Paragraph p) { - try { - notebookServer.getJobManagerService().getNoteJobInfo(p.getNote().getId(), null, - new JobManagerServiceCallback()); - } catch (IOException e) { - LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); - } - } + @Override + public void onParagraphUpdate(Paragraph p) throws IOException { - @Override - public void onNoteCreate(Note note) { - try { - notebookServer.getJobManagerService().getNoteJobInfo(note.getId(), null, - new JobManagerServiceCallback()); - } catch (IOException e) { - LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); - } + } + + @Override + public void onNoteCreate(Note note, AuthenticationInfo subject) { + try { + getJobManagerService().getNoteJobInfo(note.getId(), null, + new JobManagerServiceCallback()); + } catch (IOException e) { + LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); } + } - @Override - public void onParagraphStatusChange(Paragraph p, Status status) { - try { - notebookServer.getJobManagerService().getNoteJobInfo(p.getNote().getId(), null, - new JobManagerServiceCallback()); - } catch (IOException e) { - LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); - } + @Override + public void onNoteUpdate(Note note, AuthenticationInfo subject) throws IOException { + + } + + @Override + public void onParagraphStatusChange(Paragraph p, Status status) { + try { + getJobManagerService().getNoteJobInfo(p.getNote().getId(), null, + new JobManagerServiceCallback()); + } catch (IOException e) { + LOG.warn("can not broadcast for job manager: " + e.getMessage(), e); } + } - private class JobManagerServiceCallback - extends SimpleServiceCallback<List<JobManagerService.NoteJobInfo>> { - @Override - public void onSuccess(List<JobManagerService.NoteJobInfo> notesJobInfo, - ServiceContext context) throws IOException { - super.onSuccess(notesJobInfo, context); - Map<String, Object> response = new HashMap<>(); - response.put("lastResponseUnixTime", System.currentTimeMillis()); - response.put("jobs", notesJobInfo); - connectionManager.broadcast(JobManagerServiceType.JOB_MANAGER_PAGE.getKey(), - new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response)); - } + + private class JobManagerServiceCallback + extends SimpleServiceCallback<List<JobManagerService.NoteJobInfo>> { + @Override + public void onSuccess(List<JobManagerService.NoteJobInfo> notesJobInfo, + ServiceContext context) throws IOException { + super.onSuccess(notesJobInfo, context); + Map<String, Object> response = new HashMap<>(); + response.put("lastResponseUnixTime", System.currentTimeMillis()); + response.put("jobs", notesJobInfo); + connectionManager.broadcast(JobManagerServiceType.JOB_MANAGER_PAGE.getKey(), + new Message(OP.LIST_UPDATE_NOTE_JOBS).put("noteRunningJobs", response)); } } + @Override public void onProgressUpdate(Paragraph p, int progress) { connectionManager.broadcast(p.getNote().getId(), @@ -1938,7 +1597,7 @@ public class NotebookServer extends WebSocketServlet } try { - p.getNote().persist(p.getAuthenticationInfo()); + notebook().saveNote(p.getNote(), p.getAuthenticationInfo()); } catch (IOException e) { LOG.error(e.toString(), e); } @@ -1946,9 +1605,9 @@ public class NotebookServer extends WebSocketServlet p.setStatusToUserParagraph(p.getStatus()); broadcastParagraph(p.getNote(), p); - for (NotebookEventListener listener : notebook().getNotebookEventListeners()) { - listener.onParagraphStatusChange(p, after); - } + // for (NoteEventListener listener : notebook().getNoteEventListeners()) { + // listener.onParagraphStatusChange(p, after); + // } try { broadcastUpdateNoteJobInfo(System.currentTimeMillis() - 5000); @@ -1985,11 +1644,6 @@ public class NotebookServer extends WebSocketServlet // TODO } - - public NotebookEventListener getNotebookInformationListener() { - return new NotebookInformationListener(this); - } - private void sendAllAngularObjects(Note note, String user, NotebookSocket conn) throws IOException { List<InterpreterSetting> settings = @@ -2202,13 +1856,17 @@ public class NotebookServer extends WebSocketServlet public void onFailure(Exception ex, ServiceContext context) throws IOException { super.onFailure(ex, context); if (ex instanceof ForbiddenException) { - Type type = new TypeToken<Map<String, String>>(){}.getType(); + Type type = new TypeToken<Map<String, String>>() {}.getType(); Map<String, String> jsonObject = gson.fromJson(((ForbiddenException) ex).getResponse().getEntity().toString(), type); conn.send(serializeMessage(new Message(OP.AUTH_INFO) .put("info", jsonObject.get("message")))); } else { - conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info", ex.getMessage()))); + String message = ex.getMessage(); + if (ex.getCause() != null) { + message += ", cause: " + ex.getCause().getMessage(); + } + conn.send(serializeMessage(new Message(OP.ERROR_INFO).put("info", message))); } } } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/085efeb6/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java ---------------------------------------------------------------------- diff --git a/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java b/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java index fcf678a..dfb442a 100644 --- a/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java +++ b/zeppelin-server/src/test/java/org/apache/zeppelin/recovery/RecoveryTest.java @@ -66,7 +66,7 @@ public class RecoveryTest extends AbstractTestRestApi { @Test public void testRecovery() throws Exception { - Note note1 = ZeppelinServer.notebook.createNote(AuthenticationInfo.ANONYMOUS); + Note note1 = ZeppelinServer.notebook.createNote("note1", AuthenticationInfo.ANONYMOUS); // run python interpreter and create new variable `user` Paragraph p1 = note1.addNewParagraph(AuthenticationInfo.ANONYMOUS); @@ -78,10 +78,11 @@ public class RecoveryTest extends AbstractTestRestApi { assertEquals(resp.get("status"), "OK"); post.releaseConnection(); assertEquals(Job.Status.FINISHED, p1.getStatus()); + ZeppelinServer.notebook.saveNote(note1, AuthenticationInfo.ANONYMOUS); // shutdown zeppelin and restart it shutDown(); - startUp(RecoveryTest.class.getSimpleName()); + startUp(RecoveryTest.class.getSimpleName(), false); // run the paragraph again, but change the text to print variable `user` note1 = ZeppelinServer.notebook.getNote(note1.getId()); @@ -96,7 +97,7 @@ public class RecoveryTest extends AbstractTestRestApi { @Test public void testRecovery_2() throws Exception { - Note note1 = ZeppelinServer.notebook.createNote(AuthenticationInfo.ANONYMOUS); + Note note1 = ZeppelinServer.notebook.createNote("note2", AuthenticationInfo.ANONYMOUS); // run python interpreter and create new variable `user` Paragraph p1 = note1.addNewParagraph(AuthenticationInfo.ANONYMOUS); @@ -108,8 +109,7 @@ public class RecoveryTest extends AbstractTestRestApi { assertEquals(resp.get("status"), "OK"); post.releaseConnection(); assertEquals(Job.Status.FINISHED, p1.getStatus()); - note1.persist(AuthenticationInfo.ANONYMOUS); - + ZeppelinServer.notebook.saveNote(note1, AuthenticationInfo.ANONYMOUS); // restart the python interpreter ZeppelinServer.notebook.getInterpreterSettingManager().restart( ((ManagedInterpreterGroup) p1.getBindedInterpreter().getInterpreterGroup()) @@ -118,7 +118,7 @@ public class RecoveryTest extends AbstractTestRestApi { // shutdown zeppelin and restart it shutDown(); - startUp(RecoveryTest.class.getSimpleName()); + startUp(RecoveryTest.class.getSimpleName(), false); // run the paragraph again, but change the text to print variable `user`. // can not recover the python interpreter, because it has been shutdown. @@ -133,7 +133,7 @@ public class RecoveryTest extends AbstractTestRestApi { @Test public void testRecovery_3() throws Exception { - Note note1 = ZeppelinServer.notebook.createNote(AuthenticationInfo.ANONYMOUS); + Note note1 = ZeppelinServer.notebook.createNote("note3", AuthenticationInfo.ANONYMOUS); // run python interpreter and create new variable `user` Paragraph p1 = note1.addNewParagraph(AuthenticationInfo.ANONYMOUS); @@ -145,13 +145,13 @@ public class RecoveryTest extends AbstractTestRestApi { assertEquals(resp.get("status"), "OK"); post.releaseConnection(); assertEquals(Job.Status.FINISHED, p1.getStatus()); - note1.persist(AuthenticationInfo.ANONYMOUS); + ZeppelinServer.notebook.saveNote(note1, AuthenticationInfo.ANONYMOUS); // shutdown zeppelin and restart it shutDown(); StopInterpreter.main(new String[]{}); - startUp(RecoveryTest.class.getSimpleName()); + startUp(RecoveryTest.class.getSimpleName(), false); // run the paragraph again, but change the text to print variable `user`. // can not recover the python interpreter, because it has been shutdown.