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.

Reply via email to