/** set note authorization information */
  @PUT
  @Path("{noteId}/permissions")
  @ZeppelinApi
  public Response putNotePermissions(@PathParam("noteId") String noteId, String req)
      throws IOException {
    HashMap<String, HashSet> permMap =
        gson.fromJson(req, new TypeToken<HashMap<String, HashSet>>() {}.getType());
    Note note = notebook.getNote(noteId);
    String principal = SecurityUtils.getPrincipal();
    HashSet<String> roles = SecurityUtils.getRoles();
    LOG.info(
        "Set permissions {} {} {} {} {}",
        noteId,
        principal,
        permMap.get("owners"),
        permMap.get("readers"),
        permMap.get("writers"));

    HashSet<String> userAndRoles = new HashSet<String>();
    userAndRoles.add(principal);
    userAndRoles.addAll(roles);
    if (!notebookAuthorization.isOwner(noteId, userAndRoles)) {
      return new JsonResponse<>(
              Status.FORBIDDEN,
              ownerPermissionError(userAndRoles, notebookAuthorization.getOwners(noteId)))
          .build();
    }

    HashSet readers = permMap.get("readers");
    HashSet owners = permMap.get("owners");
    HashSet writers = permMap.get("writers");
    // Set readers, if writers and owners is empty -> set to user requesting the change
    if (readers != null && !readers.isEmpty()) {
      if (writers.isEmpty()) {
        writers = Sets.newHashSet(SecurityUtils.getPrincipal());
      }
      if (owners.isEmpty()) {
        owners = Sets.newHashSet(SecurityUtils.getPrincipal());
      }
    }
    // Set writers, if owners is empty -> set to user requesting the change
    if (writers != null && !writers.isEmpty()) {
      if (owners.isEmpty()) {
        owners = Sets.newHashSet(SecurityUtils.getPrincipal());
      }
    }

    notebookAuthorization.setReaders(noteId, readers);
    notebookAuthorization.setWriters(noteId, writers);
    notebookAuthorization.setOwners(noteId, owners);
    LOG.debug(
        "After set permissions {} {} {}",
        notebookAuthorization.getOwners(noteId),
        notebookAuthorization.getReaders(noteId),
        notebookAuthorization.getWriters(noteId));
    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
    note.persist(subject);
    notebookServer.broadcastNote(note);
    return new JsonResponse<>(Status.OK).build();
  }
  /**
   * Run paragraph job REST API
   *
   * @param message - JSON with params if user wants to update dynamic form's value null, empty
   *     string, empty json if user doesn't want to update
   * @return JSON with status.OK
   * @throws IOException, IllegalArgumentException
   */
  @POST
  @Path("job/{notebookId}/{paragraphId}")
  @ZeppelinApi
  public Response runParagraph(
      @PathParam("notebookId") String notebookId,
      @PathParam("paragraphId") String paragraphId,
      String message)
      throws IOException, IllegalArgumentException {
    LOG.info("run paragraph job {} {} {}", notebookId, paragraphId, message);

    Note note = notebook.getNote(notebookId);
    if (note == null) {
      return new JsonResponse<>(Status.NOT_FOUND, "note not found.").build();
    }

    Paragraph paragraph = note.getParagraph(paragraphId);
    if (paragraph == null) {
      return new JsonResponse<>(Status.NOT_FOUND, "paragraph not found.").build();
    }

    // handle params if presented
    if (!StringUtils.isEmpty(message)) {
      RunParagraphWithParametersRequest request =
          gson.fromJson(message, RunParagraphWithParametersRequest.class);
      Map<String, Object> paramsForUpdating = request.getParams();
      if (paramsForUpdating != null) {
        paragraph.settings.getParams().putAll(paramsForUpdating);
        AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
        note.persist(subject);
      }
    }

    note.run(paragraph.getId());
    return new JsonResponse<>(Status.OK).build();
  }
  /**
   * Delete paragraph REST API
   *
   * @param
   * @return JSON with status.OK
   * @throws IOException
   */
  @DELETE
  @Path("{notebookId}/paragraph/{paragraphId}")
  @ZeppelinApi
  public Response deleteParagraph(
      @PathParam("notebookId") String notebookId, @PathParam("paragraphId") String paragraphId)
      throws IOException {
    LOG.info("delete paragraph {} {}", notebookId, paragraphId);

    Note note = notebook.getNote(notebookId);
    if (note == null) {
      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
    }

    Paragraph p = note.getParagraph(paragraphId);
    if (p == null) {
      return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build();
    }

    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
    note.removeParagraph(paragraphId);
    note.persist(subject);
    notebookServer.broadcastNote(note);

    return new JsonResponse(Status.OK, "").build();
  }
  /**
   * Move paragraph REST API
   *
   * @param newIndex - new index to move
   * @return JSON with status.OK
   * @throws IOException
   */
  @POST
  @Path("{notebookId}/paragraph/{paragraphId}/move/{newIndex}")
  @ZeppelinApi
  public Response moveParagraph(
      @PathParam("notebookId") String notebookId,
      @PathParam("paragraphId") String paragraphId,
      @PathParam("newIndex") String newIndex)
      throws IOException {
    LOG.info("move paragraph {} {} {}", notebookId, paragraphId, newIndex);

    Note note = notebook.getNote(notebookId);
    if (note == null) {
      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
    }

    Paragraph p = note.getParagraph(paragraphId);
    if (p == null) {
      return new JsonResponse(Status.NOT_FOUND, "paragraph not found.").build();
    }

    try {
      note.moveParagraph(paragraphId, Integer.parseInt(newIndex), true);

      AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
      note.persist(subject);
      notebookServer.broadcastNote(note);
      return new JsonResponse(Status.OK, "").build();
    } catch (IndexOutOfBoundsException e) {
      LOG.error("Exception in NotebookRestApi while moveParagraph ", e);
      return new JsonResponse(Status.BAD_REQUEST, "paragraph's new index is out of bound").build();
    }
  }
  /**
   * Insert paragraph REST API
   *
   * @param message - JSON containing paragraph's information
   * @return JSON with status.OK
   * @throws IOException
   */
  @POST
  @Path("{notebookId}/paragraph")
  @ZeppelinApi
  public Response insertParagraph(@PathParam("notebookId") String notebookId, String message)
      throws IOException {
    LOG.info("insert paragraph {} {}", notebookId, message);

    Note note = notebook.getNote(notebookId);
    if (note == null) {
      return new JsonResponse(Status.NOT_FOUND, "note not found.").build();
    }

    NewParagraphRequest request = gson.fromJson(message, NewParagraphRequest.class);

    Paragraph p;
    Double indexDouble = request.getIndex();
    if (indexDouble == null) {
      p = note.addParagraph();
    } else {
      p = note.insertParagraph(indexDouble.intValue());
    }
    p.setTitle(request.getTitle());
    p.setText(request.getText());

    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
    note.persist(subject);
    notebookServer.broadcastNote(note);
    return new JsonResponse(Status.CREATED, "", p.getId()).build();
  }
  /**
   * Create new note REST API
   *
   * @param message - JSON with new note name
   * @return JSON with new note ID
   * @throws IOException
   */
  @POST
  @Path("/")
  @ZeppelinApi
  public Response createNote(String message) throws IOException {
    LOG.info("Create new notebook by JSON {}", message);
    NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class);
    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
    Note note = notebook.createNote(subject);
    List<NewParagraphRequest> initialParagraphs = request.getParagraphs();
    if (initialParagraphs != null) {
      for (NewParagraphRequest paragraphRequest : initialParagraphs) {
        Paragraph p = note.addParagraph();
        p.setTitle(paragraphRequest.getTitle());
        p.setText(paragraphRequest.getText());
      }
    }
    note.addParagraph(); // add one paragraph to the last
    String noteName = request.getName();
    if (noteName.isEmpty()) {
      noteName = "Note " + note.getId();
    }

    note.setName(noteName);
    note.persist(subject);
    notebookServer.broadcastNote(note);
    notebookServer.broadcastNoteList(subject);
    return new JsonResponse<>(Status.CREATED, "", note.getId()).build();
  }
 /**
  * import new note REST API
  *
  * @param req - notebook Json
  * @return JSON with new note ID
  * @throws IOException
  */
 @POST
 @Path("import")
 @ZeppelinApi
 public Response importNotebook(String req) throws IOException {
   AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
   Note newNote = notebook.importNote(req, null, subject);
   return new JsonResponse<>(Status.CREATED, "", newNote.getId()).build();
 }
 @GET
 @Path("/")
 @ZeppelinApi
 public Response getNotebookList() throws IOException {
   AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
   List<Map<String, String>> notesInfo = notebookServer.generateNotebooksInfo(false, subject);
   return new JsonResponse<>(Status.OK, "", notesInfo).build();
 }
 /**
  * Clone note REST API
  *
  * @param
  * @return JSON with status.CREATED
  * @throws IOException, CloneNotSupportedException, IllegalArgumentException
  */
 @POST
 @Path("{notebookId}")
 @ZeppelinApi
 public Response cloneNote(@PathParam("notebookId") String notebookId, String message)
     throws IOException, CloneNotSupportedException, IllegalArgumentException {
   LOG.info("clone notebook by JSON {}", message);
   NewNotebookRequest request = gson.fromJson(message, NewNotebookRequest.class);
   String newNoteName = request.getName();
   AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
   Note newNote = notebook.cloneNote(notebookId, newNoteName, subject);
   notebookServer.broadcastNote(newNote);
   notebookServer.broadcastNoteList(subject);
   return new JsonResponse<>(Status.CREATED, "", newNote.getId()).build();
 }
  /**
   * Get notebook jobs for job manager
   *
   * @param
   * @return JSON with status.OK
   * @throws IOException, IllegalArgumentException
   */
  @GET
  @Path("jobmanager/")
  @ZeppelinApi
  public Response getJobListforNotebook() throws IOException, IllegalArgumentException {
    LOG.info("Get notebook jobs for job manager");

    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
    List<Map<String, Object>> notebookJobs = notebook.getJobListforNotebook(false, 0, subject);
    Map<String, Object> response = new HashMap<>();

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

    return new JsonResponse<>(Status.OK, response).build();
  }
  /**
   * Delete note REST API
   *
   * @param
   * @return JSON with status.OK
   * @throws IOException
   */
  @DELETE
  @Path("{notebookId}")
  @ZeppelinApi
  public Response deleteNote(@PathParam("notebookId") String notebookId) throws IOException {
    LOG.info("Delete notebook {} ", notebookId);
    AuthenticationInfo subject = new AuthenticationInfo(SecurityUtils.getPrincipal());
    if (!(notebookId.isEmpty())) {
      Note note = notebook.getNote(notebookId);
      if (note != null) {
        notebook.removeNote(notebookId, subject);
      }
    }

    notebookServer.broadcastNoteList(subject);
    return new JsonResponse<>(Status.OK, "").build();
  }
 /** Search for a Notes with permissions */
 @GET
 @Path("search")
 @ZeppelinApi
 public Response search(@QueryParam("q") String queryTerm) {
   LOG.info("Searching notebooks for: {}", queryTerm);
   String principal = SecurityUtils.getPrincipal();
   HashSet<String> roles = SecurityUtils.getRoles();
   HashSet<String> userAndRoles = new HashSet<String>();
   userAndRoles.add(principal);
   userAndRoles.addAll(roles);
   List<Map<String, String>> notebooksFound = notebookIndex.query(queryTerm);
   for (int i = 0; i < notebooksFound.size(); i++) {
     String[] Id = notebooksFound.get(i).get("id").split("/", 2);
     String noteId = Id[0];
     if (!notebookAuthorization.isOwner(noteId, userAndRoles)
         && !notebookAuthorization.isReader(noteId, userAndRoles)
         && !notebookAuthorization.isWriter(noteId, userAndRoles)) {
       notebooksFound.remove(i);
       i--;
     }
   }
   LOG.info("{} notebooks found", notebooksFound.size());
   return new JsonResponse<>(Status.OK, notebooksFound).build();
 }