/**
   * Retrieves the specified attachment from a ticket.
   *
   * @param repository
   * @param ticketId
   * @param filename
   * @return an attachment, if found, null otherwise
   */
  @Override
  public Attachment getAttachment(RepositoryModel repository, long ticketId, String filename) {
    if (ticketId <= 0L) {
      return null;
    }

    // deserialize the ticket model so that we have the attachment metadata
    TicketModel ticket = getTicket(repository, ticketId);
    Attachment attachment = ticket.getAttachment(filename);

    // attachment not found
    if (attachment == null) {
      return null;
    }

    // retrieve the attachment content
    Repository db = repositoryManager.getRepository(repository.name);
    try {
      String attachmentPath = toAttachmentPath(ticketId, attachment.name);
      RevTree tree = JGitUtils.getCommit(db, BRANCH).getTree();
      byte[] content = JGitUtils.getByteContent(db, tree, attachmentPath, false);
      attachment.content = content;
      attachment.size = content.length;
      return attachment;
    } finally {
      db.close();
    }
  }
 /**
  * Retrieves the ticket from the repository by first looking-up the changeId associated with the
  * ticketId.
  *
  * @param repository
  * @param ticketId
  * @return a ticket, if it exists, otherwise null
  */
 @Override
 protected TicketModel getTicketImpl(RepositoryModel repository, long ticketId) {
   Repository db = repositoryManager.getRepository(repository.name);
   try {
     List<Change> changes = getJournal(db, ticketId);
     if (ArrayUtils.isEmpty(changes)) {
       log.warn("Empty journal for {}:{}", repository, ticketId);
       return null;
     }
     TicketModel ticket = TicketModel.buildTicket(changes);
     if (ticket != null) {
       ticket.project = repository.projectPath;
       ticket.repository = repository.name;
       ticket.number = ticketId;
     }
     return ticket;
   } finally {
     db.close();
   }
 }
  /**
   * Returns all the tickets in the repository. Querying tickets from the repository requires
   * deserializing all tickets. This is an expensive process and not recommended. Tickets are
   * indexed by Lucene and queries should be executed against that index.
   *
   * @param repository
   * @param filter optional filter to only return matching results
   * @return a list of tickets
   */
  @Override
  public List<TicketModel> getTickets(RepositoryModel repository, TicketFilter filter) {
    List<TicketModel> list = new ArrayList<TicketModel>();

    Repository db = repositoryManager.getRepository(repository.name);
    try {
      RefModel ticketsBranch = getTicketsBranch(db);
      if (ticketsBranch == null) {
        return list;
      }

      // Collect the set of all json files
      List<PathModel> paths = JGitUtils.getDocuments(db, Arrays.asList("json"), BRANCH);

      // Deserialize each ticket and optionally filter out unwanted tickets
      for (PathModel path : paths) {
        String name = path.name.substring(path.name.lastIndexOf('/') + 1);
        if (!JOURNAL.equals(name)) {
          continue;
        }
        String json = readTicketsFile(db, path.path);
        if (StringUtils.isEmpty(json)) {
          // journal was touched but no changes were written
          continue;
        }
        try {
          // Reconstruct ticketId from the path
          // id/26/326/journal.json
          String tid = path.path.split("/")[2];
          long ticketId = Long.parseLong(tid);
          List<Change> changes = TicketSerializer.deserializeJournal(json);
          if (ArrayUtils.isEmpty(changes)) {
            log.warn("Empty journal for {}:{}", repository, path.path);
            continue;
          }
          TicketModel ticket = TicketModel.buildTicket(changes);
          ticket.project = repository.projectPath;
          ticket.repository = repository.name;
          ticket.number = ticketId;

          // add the ticket, conditionally, to the list
          if (filter == null) {
            list.add(ticket);
          } else {
            if (filter.accept(ticket)) {
              list.add(ticket);
            }
          }
        } catch (Exception e) {
          log.error(
              "failed to deserialize {}/{}\n{}",
              new Object[] {repository, path.path, e.getMessage()});
          log.error(null, e);
        }
      }

      // sort the tickets by creation
      Collections.sort(list);
      return list;
    } finally {
      db.close();
    }
  }