James Moger
2014-04-17 1d78b8b372f15d89f10fd32cb0227a6a7966de3c
src/main/java/com/gitblit/tickets/ITicketService.java
@@ -35,7 +35,9 @@
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.extensions.TicketHook;
import com.gitblit.manager.INotificationManager;
import com.gitblit.manager.IPluginManager;
import com.gitblit.manager.IRepositoryManager;
import com.gitblit.manager.IRuntimeManager;
import com.gitblit.manager.IUserManager;
@@ -94,6 +96,8 @@
   protected final IRepositoryManager repositoryManager;
   protected final IPluginManager pluginManager;
   protected final TicketIndexer indexer;
   private final Cache<TicketKey, TicketModel> ticketsCache;
@@ -136,6 +140,7 @@
    */
   public ITicketService(
         IRuntimeManager runtimeManager,
         IPluginManager pluginManager,
         INotificationManager notificationManager,
         IUserManager userManager,
         IRepositoryManager repositoryManager) {
@@ -143,6 +148,7 @@
      this.log = LoggerFactory.getLogger(getClass());
      this.settings = runtimeManager.getSettings();
      this.runtimeManager = runtimeManager;
      this.pluginManager = pluginManager;
      this.notificationManager = notificationManager;
      this.userManager = userManager;
      this.repositoryManager = repositoryManager;
@@ -161,13 +167,13 @@
   /**
    * Start the service.
    *
    * @since 1.4.0
    */
   public abstract ITicketService start();
   /**
    * Stop the service.
    *
    * @since 1.4.0
    */
   public final ITicketService stop() {
      indexer.close();
@@ -179,7 +185,7 @@
   /**
    * Creates a ticket notifier.  The ticket notifier is not thread-safe!
    *
    * @since 1.4.0
    */
   public TicketNotifier createNotifier() {
      return new TicketNotifier(
@@ -194,6 +200,7 @@
    * Returns the ready status of the ticket service.
    *
    * @return true if the ticket service is ready
    * @since 1.4.0
    */
   public boolean isReady() {
      return true;
@@ -204,6 +211,7 @@
    *
    * @param repository
    * @return true if patchsets are being accepted
    * @since 1.4.0
    */
   public boolean isAcceptingNewPatchsets(RepositoryModel repository) {
      return isReady()
@@ -218,6 +226,7 @@
    *
    * @param repository
    * @return true if tickets are being accepted
    * @since 1.4.0
    */
   public boolean isAcceptingNewTickets(RepositoryModel repository) {
      return isReady()
@@ -231,6 +240,7 @@
    *
    * @param repository
    * @return true if tickets are allowed to be updated
    * @since 1.4.0
    */
   public boolean isAcceptingTicketUpdates(RepositoryModel repository) {
      return isReady()
@@ -243,6 +253,7 @@
    * Returns true if the repository has any tickets
    * @param repository
    * @return true if the repository has tickets
    * @since 1.4.0
    */
   public boolean hasTickets(RepositoryModel repository) {
      return indexer.hasTickets(repository);
@@ -250,11 +261,13 @@
   /**
    * Closes any open resources used by this service.
    * @since 1.4.0
    */
   protected abstract void close();
   /**
    * Reset all caches in the service.
    * @since 1.4.0
    */
   public final synchronized void resetCaches() {
      ticketsCache.invalidateAll();
@@ -263,10 +276,15 @@
      resetCachesImpl();
   }
   /**
    * Reset all caches in the service.
    * @since 1.4.0
    */
   protected abstract void resetCachesImpl();
   /**
    * Reset any caches for the repository in the service.
    * @since 1.4.0
    */
   public final synchronized void resetCaches(RepositoryModel repository) {
      List<TicketKey> repoKeys = new ArrayList<TicketKey>();
@@ -281,6 +299,12 @@
      resetCachesImpl(repository);
   }
   /**
    * Reset the caches for the specified repository.
    *
    * @param repository
    * @since 1.4.0
    */
   protected abstract void resetCachesImpl(RepositoryModel repository);
@@ -289,6 +313,7 @@
    *
    * @param repository
    * @return the list of labels
    * @since 1.4.0
    */
   public List<TicketLabel> getLabels(RepositoryModel repository) {
      String key = repository.name;
@@ -321,6 +346,7 @@
    * @param repository
    * @param label
    * @return a TicketLabel
    * @since 1.4.0
    */
   public TicketLabel getLabel(RepositoryModel repository, String label) {
      for (TicketLabel tl : getLabels(repository)) {
@@ -340,6 +366,7 @@
    * @param milestone
    * @param createdBy
    * @return the label
    * @since 1.4.0
    */
   public synchronized TicketLabel createLabel(RepositoryModel repository, String label, String createdBy) {
      TicketLabel lb = new TicketMilestone(label);
@@ -364,6 +391,7 @@
    * @param label
    * @param createdBy
    * @return true if the update was successful
    * @since 1.4.0
    */
   public synchronized boolean updateLabel(RepositoryModel repository, TicketLabel label, String createdBy) {
      Repository db = null;
@@ -390,6 +418,7 @@
    * @param newName
    * @param createdBy
    * @return true if the rename was successful
    * @since 1.4.0
    */
   public synchronized boolean renameLabel(RepositoryModel repository, String oldName, String newName, String createdBy) {
      if (StringUtils.isEmpty(newName)) {
@@ -427,6 +456,7 @@
    * @param label
    * @param createdBy
    * @return true if the delete was successful
    * @since 1.4.0
    */
   public synchronized boolean deleteLabel(RepositoryModel repository, String label, String createdBy) {
      if (StringUtils.isEmpty(label)) {
@@ -453,6 +483,7 @@
    *
    * @param repository
    * @return the list of milestones
    * @since 1.4.0
    */
   public List<TicketMilestone> getMilestones(RepositoryModel repository) {
      String key = repository.name;
@@ -494,6 +525,7 @@
    * @param repository
    * @param status
    * @return the list of milestones
    * @since 1.4.0
    */
   public List<TicketMilestone> getMilestones(RepositoryModel repository, Status status) {
      List<TicketMilestone> matches = new ArrayList<TicketMilestone>();
@@ -511,6 +543,7 @@
    * @param repository
    * @param milestone
    * @return the milestone or null if it does not exist
    * @since 1.4.0
    */
   public TicketMilestone getMilestone(RepositoryModel repository, String milestone) {
      for (TicketMilestone ms : getMilestones(repository)) {
@@ -530,6 +563,7 @@
    * @param milestone
    * @param createdBy
    * @return the milestone
    * @since 1.4.0
    */
   public synchronized TicketMilestone createMilestone(RepositoryModel repository, String milestone, String createdBy) {
      TicketMilestone ms = new TicketMilestone(milestone);
@@ -557,6 +591,7 @@
    * @param milestone
    * @param createdBy
    * @return true if successful
    * @since 1.4.0
    */
   public synchronized boolean updateMilestone(RepositoryModel repository, TicketMilestone milestone, String createdBy) {
      Repository db = null;
@@ -589,6 +624,7 @@
    * @param newName
    * @param createdBy
    * @return true if successful
    * @since 1.4.0
    */
   public synchronized boolean renameMilestone(RepositoryModel repository, String oldName, String newName, String createdBy) {
      if (StringUtils.isEmpty(newName)) {
@@ -627,6 +663,7 @@
      }
      return false;
   }
   /**
    * Deletes a milestone.
    *
@@ -634,6 +671,7 @@
    * @param milestone
    * @param createdBy
    * @return true if successful
    * @since 1.4.0
    */
   public synchronized boolean deleteMilestone(RepositoryModel repository, String milestone, String createdBy) {
      if (StringUtils.isEmpty(milestone)) {
@@ -662,6 +700,7 @@
    *
    * @param repository
    * @return a new ticket id
    * @since 1.4.0
    */
   public abstract long assignNewId(RepositoryModel repository);
@@ -671,6 +710,7 @@
    * @param repository
    * @param ticketId
    * @return true if the ticket exists
    * @since 1.4.0
    */
   public abstract boolean hasTicket(RepositoryModel repository, long ticketId);
@@ -679,6 +719,7 @@
    *
    * @param repository
    * @return all tickets
    * @since 1.4.0
    */
   public List<TicketModel> getTickets(RepositoryModel repository) {
      return getTickets(repository, null);
@@ -694,6 +735,7 @@
    * @param filter
    *            optional issue filter to only return matching results
    * @return a list of tickets
    * @since 1.4.0
    */
   public abstract List<TicketModel> getTickets(RepositoryModel repository, TicketFilter filter);
@@ -703,31 +745,35 @@
    * @param repository
    * @param ticketId
    * @return a ticket, if it exists, otherwise null
    * @since 1.4.0
    */
   public final TicketModel getTicket(RepositoryModel repository, long ticketId) {
      TicketKey key = new TicketKey(repository, ticketId);
      TicketModel ticket = ticketsCache.getIfPresent(key);
      // if ticket not cached
      if (ticket == null) {
         // load & cache ticket
         //load ticket
         ticket = getTicketImpl(repository, ticketId);
         if (ticket.hasPatchsets()) {
            Repository r = repositoryManager.getRepository(repository.name);
            try {
               Patchset patchset = ticket.getCurrentPatchset();
               DiffStat diffStat = DiffUtils.getDiffStat(r, patchset.base, patchset.tip);
               // diffstat could be null if we have ticket data without the
               // commit objects.  e.g. ticket replication without repo
               // mirroring
               if (diffStat != null) {
                  ticket.insertions = diffStat.getInsertions();
                  ticket.deletions = diffStat.getDeletions();
               }
            } finally {
               r.close();
            }
         }
         // if ticket exists
         if (ticket != null) {
            if (ticket.hasPatchsets()) {
               Repository r = repositoryManager.getRepository(repository.name);
               try {
                  Patchset patchset = ticket.getCurrentPatchset();
                  DiffStat diffStat = DiffUtils.getDiffStat(r, patchset.base, patchset.tip);
                  // diffstat could be null if we have ticket data without the
                  // commit objects.  e.g. ticket replication without repo
                  // mirroring
                  if (diffStat != null) {
                     ticket.insertions = diffStat.getInsertions();
                     ticket.deletions = diffStat.getDeletions();
                  }
               } finally {
                  r.close();
               }
            }
            //cache ticket
            ticketsCache.put(key, ticket);
         }
      }
@@ -740,6 +786,7 @@
    * @param repository
    * @param ticketId
    * @return a ticket, if it exists, otherwise null
    * @since 1.4.0
    */
   protected abstract TicketModel getTicketImpl(RepositoryModel repository, long ticketId);
@@ -748,6 +795,7 @@
    *
    * @param ticket
    * @return the ticket url
    * @since 1.4.0
    */
   public String getTicketUrl(TicketModel ticket) {
      final String canonicalUrl = settings.getString(Keys.web.canonicalUrl, "https://localhost:8443");
@@ -761,6 +809,7 @@
    * @param base
    * @param tip
    * @return the compare url
    * @since 1.4.0
    */
   public String getCompareUrl(TicketModel ticket, String base, String tip) {
      final String canonicalUrl = settings.getString(Keys.web.canonicalUrl, "https://localhost:8443");
@@ -772,6 +821,7 @@
    * Returns true if attachments are supported.
    *
    * @return true if attachments are supported
    * @since 1.4.0
    */
   public abstract boolean supportsAttachments();
@@ -782,6 +832,7 @@
    * @param ticketId
    * @param filename
    * @return an attachment, if found, null otherwise
    * @since 1.4.0
    */
   public abstract Attachment getAttachment(RepositoryModel repository, long ticketId, String filename);
@@ -793,6 +844,7 @@
    * @param repository
    * @param change
    * @return true if successful
    * @since 1.4.0
    */
   public TicketModel createTicket(RepositoryModel repository, Change change) {
      return createTicket(repository, 0L, change);
@@ -807,6 +859,7 @@
    * @param ticketId (if <=0 the ticket id will be assigned)
    * @param change
    * @return true if successful
    * @since 1.4.0
    */
   public TicketModel createTicket(RepositoryModel repository, long ticketId, Change change) {
@@ -832,6 +885,17 @@
      if (success) {
         TicketModel ticket = getTicket(repository, ticketId);
         indexer.index(ticket);
         // call the ticket hooks
         if (pluginManager != null) {
            for (TicketHook hook : pluginManager.getExtensions(TicketHook.class)) {
               try {
                  hook.onNewTicket(ticket);
               } catch (Exception e) {
                  log.error("Failed to execute extension", e);
               }
            }
         }
         return ticket;
      }
      return null;
@@ -844,6 +908,7 @@
    * @param ticketId
    * @param change
    * @return the ticket model if successful
    * @since 1.4.0
    */
   public final TicketModel updateTicket(RepositoryModel repository, long ticketId, Change change) {
      if (change == null) {
@@ -862,6 +927,17 @@
         TicketModel ticket = getTicket(repository, ticketId);
         ticketsCache.put(key, ticket);
         indexer.index(ticket);
         // call the ticket hooks
         if (pluginManager != null) {
            for (TicketHook hook : pluginManager.getExtensions(TicketHook.class)) {
               try {
                  hook.onUpdateTicket(ticket, change);
               } catch (Exception e) {
                  log.error("Failed to execute extension", e);
               }
            }
         }
         return ticket;
      }
      return null;
@@ -871,6 +947,7 @@
    * Deletes all tickets in every repository.
    *
    * @return true if successful
    * @since 1.4.0
    */
   public boolean deleteAll() {
      List<String> repositories = repositoryManager.getRepositoryList();
@@ -893,6 +970,7 @@
    * Deletes all tickets in the specified repository.
    * @param repository
    * @return true if succesful
    * @since 1.4.0
    */
   public boolean deleteAll(RepositoryModel repository) {
      boolean success = deleteAllImpl(repository);
@@ -904,6 +982,12 @@
      return success;
   }
   /**
    * Delete all tickets for the specified repository.
    * @param repository
    * @return true if successful
    * @since 1.4.0
    */
   protected abstract boolean deleteAllImpl(RepositoryModel repository);
   /**
@@ -912,6 +996,7 @@
    * @param oldRepositoryName
    * @param newRepositoryName
    * @return true if successful
    * @since 1.4.0
    */
   public boolean rename(RepositoryModel oldRepository, RepositoryModel newRepository) {
      if (renameImpl(oldRepository, newRepository)) {
@@ -923,6 +1008,14 @@
      return false;
   }
   /**
    * Renames a repository.
    *
    * @param oldRepository
    * @param newRepository
    * @return true if successful
    * @since 1.4.0
    */
   protected abstract boolean renameImpl(RepositoryModel oldRepository, RepositoryModel newRepository);
   /**
@@ -932,6 +1025,7 @@
    * @param ticketId
    * @param deletedBy
    * @return true if successful
    * @since 1.4.0
    */
   public boolean deleteTicket(RepositoryModel repository, long ticketId, String deletedBy) {
      TicketModel ticket = getTicket(repository, ticketId);
@@ -953,6 +1047,7 @@
    * @param ticket
    * @param deletedBy
    * @return true if successful
    * @since 1.4.0
    */
   protected abstract boolean deleteTicketImpl(RepositoryModel repository, TicketModel ticket, String deletedBy);
@@ -968,6 +1063,7 @@
    * @param comment
    *            the revised comment
    * @return the revised ticket if the change was successful
    * @since 1.4.0
    */
   public final TicketModel updateComment(TicketModel ticket, String commentId,
         String updatedBy, String comment) {
@@ -988,6 +1084,7 @@
    * @param deletedBy
    *          the user deleting the comment
    * @return the revised ticket if the deletion was successful
    * @since 1.4.0
    */
   public final TicketModel deleteComment(TicketModel ticket, String commentId, String deletedBy) {
      Change deletion = new Change(deletedBy);
@@ -1006,6 +1103,7 @@
    * @param ticketId
    * @param change
    * @return true, if the change was committed
    * @since 1.4.0
    */
   protected abstract boolean commitChangeImpl(RepositoryModel repository, long ticketId, Change change);
@@ -1020,6 +1118,7 @@
    * @param page
    * @param pageSize
    * @return a list of matching tickets
    * @since 1.4.0
    */
   public List<QueryResult> searchFor(RepositoryModel repository, String text, int page, int pageSize) {
      return indexer.searchFor(repository, text, page, pageSize);
@@ -1034,6 +1133,7 @@
    * @param sortBy
    * @param descending
    * @return a list of matching tickets or an empty list
    * @since 1.4.0
    */
   public List<QueryResult> queryFor(String query, int page, int pageSize, String sortBy, boolean descending) {
      return indexer.queryFor(query, page, pageSize, sortBy, descending);
@@ -1042,6 +1142,7 @@
   /**
    * Destroys an existing index and reindexes all tickets.
    * This operation may be expensive and time-consuming.
    * @since 1.4.0
    */
   public void reindex() {
      long start = System.nanoTime();
@@ -1068,6 +1169,7 @@
   /**
    * Destroys any existing index and reindexes all tickets.
    * This operation may be expensive and time-consuming.
    * @since 1.4.0
    */
   public void reindex(RepositoryModel repository) {
      long start = System.nanoTime();
@@ -1085,6 +1187,7 @@
    * of ticket updates, namely merging from the web ui.
    *
    * @param runnable
    * @since 1.4.0
    */
   public synchronized void exec(Runnable runnable) {
      runnable.run();