From 3f5b8f5d9203aa7ffb7fbe9cdbaf9dba3da6cae6 Mon Sep 17 00:00:00 2001
From: Hybris95 <hybris_95@hotmail.com>
Date: Thu, 01 May 2014 16:14:15 -0400
Subject: [PATCH] Fixes sort, page building and search functions on "my tickets" page.

---
 src/main/java/com/gitblit/tickets/ITicketService.java |  246 ++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 212 insertions(+), 34 deletions(-)

diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java
index 90f9c6d..e7b4c71 100644
--- a/src/main/java/com/gitblit/tickets/ITicketService.java
+++ b/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;
@@ -47,6 +49,7 @@
 import com.gitblit.models.TicketModel.Patchset;
 import com.gitblit.models.TicketModel.Status;
 import com.gitblit.tickets.TicketIndexer.Lucene;
+import com.gitblit.utils.DeepCopier;
 import com.gitblit.utils.DiffUtils;
 import com.gitblit.utils.DiffUtils.DiffStat;
 import com.gitblit.utils.StringUtils;
@@ -94,6 +97,8 @@
 
 	protected final IRepositoryManager repositoryManager;
 
+	protected final IPluginManager pluginManager;
+
 	protected final TicketIndexer indexer;
 
 	private final Cache<TicketKey, TicketModel> ticketsCache;
@@ -136,6 +141,7 @@
 	 */
 	public ITicketService(
 			IRuntimeManager runtimeManager,
+			IPluginManager pluginManager,
 			INotificationManager notificationManager,
 			IUserManager userManager,
 			IRepositoryManager repositoryManager) {
@@ -143,6 +149,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 +168,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 +186,7 @@
 
 	/**
 	 * Creates a ticket notifier.  The ticket notifier is not thread-safe!
-	 *
+	 * @since 1.4.0
 	 */
 	public TicketNotifier createNotifier() {
 		return new TicketNotifier(
@@ -194,6 +201,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 +212,7 @@
 	 *
 	 * @param repository
 	 * @return true if patchsets are being accepted
+	 * @since 1.4.0
 	 */
 	public boolean isAcceptingNewPatchsets(RepositoryModel repository) {
 		return isReady()
@@ -218,6 +227,7 @@
 	 *
 	 * @param repository
 	 * @return true if tickets are being accepted
+	 * @since 1.4.0
 	 */
 	public boolean isAcceptingNewTickets(RepositoryModel repository) {
 		return isReady()
@@ -231,6 +241,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 +254,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 +262,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 +277,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 +300,12 @@
 		resetCachesImpl(repository);
 	}
 
+	/**
+	 * Reset the caches for the specified repository.
+	 *
+	 * @param repository
+	 * @since 1.4.0
+	 */
 	protected abstract void resetCachesImpl(RepositoryModel repository);
 
 
@@ -289,6 +314,7 @@
 	 *
 	 * @param repository
 	 * @return the list of labels
+	 * @since 1.4.0
 	 */
 	public List<TicketLabel> getLabels(RepositoryModel repository) {
 		String key = repository.name;
@@ -321,6 +347,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 +367,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);
@@ -352,7 +380,9 @@
 		} catch (IOException e) {
 			log.error("failed to create label " + label + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return lb;
 	}
@@ -364,6 +394,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;
@@ -377,7 +408,9 @@
 		} catch (IOException e) {
 			log.error("failed to update label " + label + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return false;
 	}
@@ -390,6 +423,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)) {
@@ -415,7 +449,9 @@
 		} catch (IOException e) {
 			log.error("failed to rename label " + oldName + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return false;
 	}
@@ -427,6 +463,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)) {
@@ -443,7 +480,9 @@
 		} catch (IOException e) {
 			log.error("failed to delete label " + label + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return false;
 	}
@@ -453,6 +492,7 @@
 	 *
 	 * @param repository
 	 * @return the list of milestones
+	 * @since 1.4.0
 	 */
 	public List<TicketMilestone> getMilestones(RepositoryModel repository) {
 		String key = repository.name;
@@ -494,6 +534,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,13 +552,15 @@
 	 * @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)) {
 			if (ms.name.equalsIgnoreCase(milestone)) {
+				TicketMilestone tm = DeepCopier.copy(ms);
 				String q = QueryBuilder.q(Lucene.rid.matches(repository.getRID())).and(Lucene.milestone.matches(milestone)).build();
-				ms.tickets = indexer.queryFor(q, 1, 0, Lucene.number.name(), true);
-				return ms;
+				tm.tickets = indexer.queryFor(q, 1, 0, Lucene.number.name(), true);
+				return tm;
 			}
 		}
 		return null;
@@ -530,6 +573,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);
@@ -545,7 +589,9 @@
 		} catch (IOException e) {
 			log.error("failed to create milestone " + milestone + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return ms;
 	}
@@ -557,6 +603,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;
@@ -576,7 +623,9 @@
 		} catch (IOException e) {
 			log.error("failed to update milestone " + milestone + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return false;
 	}
@@ -589,8 +638,25 @@
 	 * @param newName
 	 * @param createdBy
 	 * @return true if successful
+	 * @since 1.4.0
 	 */
 	public synchronized boolean renameMilestone(RepositoryModel repository, String oldName, String newName, String createdBy) {
+		return renameMilestone(repository, oldName, newName, createdBy, true);
+	}
+
+	/**
+	 * Renames a milestone.
+	 *
+	 * @param repository
+	 * @param oldName
+	 * @param newName
+	 * @param createdBy
+	 * @param notifyOpenTickets
+	 * @return true if successful
+	 * @since 1.6.0
+	 */
+	public synchronized boolean renameMilestone(RepositoryModel repository, String oldName,
+			String newName, String createdBy, boolean notifyOpenTickets) {
 		if (StringUtils.isEmpty(newName)) {
 			throw new IllegalArgumentException("new milestone can not be empty!");
 		}
@@ -603,7 +669,7 @@
 			config.setString(MILESTONE, newName, STATUS, milestone.status.name());
 			config.setString(MILESTONE, newName, COLOR, milestone.color);
 			if (milestone.due != null) {
-				config.setString(MILESTONE, milestone.name, DUE,
+				config.setString(MILESTONE, newName, DUE,
 						new SimpleDateFormat(DUE_DATE_PATTERN).format(milestone.due));
 			}
 			config.save();
@@ -615,18 +681,25 @@
 				Change change = new Change(createdBy);
 				change.setField(Field.milestone, newName);
 				TicketModel ticket = updateTicket(repository, qr.number, change);
-				notifier.queueMailing(ticket);
+				if (notifyOpenTickets && ticket.isOpen()) {
+					notifier.queueMailing(ticket);
+				}
 			}
-			notifier.sendAll();
+			if (notifyOpenTickets) {
+				notifier.sendAll();
+			}
 
 			return true;
 		} catch (IOException e) {
 			log.error("failed to rename milestone " + oldName + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return false;
 	}
+
 	/**
 	 * Deletes a milestone.
 	 *
@@ -634,13 +707,30 @@
 	 * @param milestone
 	 * @param createdBy
 	 * @return true if successful
+	 * @since 1.4.0
 	 */
 	public synchronized boolean deleteMilestone(RepositoryModel repository, String milestone, String createdBy) {
+		return deleteMilestone(repository, milestone, createdBy, true);
+	}
+
+	/**
+	 * Deletes a milestone.
+	 *
+	 * @param repository
+	 * @param milestone
+	 * @param createdBy
+	 * @param notifyOpenTickets
+	 * @return true if successful
+	 * @since 1.6.0
+	 */
+	public synchronized boolean deleteMilestone(RepositoryModel repository, String milestone,
+			String createdBy, boolean notifyOpenTickets) {
 		if (StringUtils.isEmpty(milestone)) {
 			throw new IllegalArgumentException("milestone can not be empty!");
 		}
 		Repository db = null;
 		try {
+			TicketMilestone tm = getMilestone(repository, milestone);
 			db = repositoryManager.getRepository(repository.name);
 			StoredConfig config = db.getConfig();
 			config.unsetSection(MILESTONE, milestone);
@@ -648,11 +738,25 @@
 
 			milestonesCache.remove(repository.name);
 
+			TicketNotifier notifier = createNotifier();
+			for (QueryResult qr : tm.tickets) {
+				Change change = new Change(createdBy);
+				change.setField(Field.milestone, "");
+				TicketModel ticket = updateTicket(repository, qr.number, change);
+				if (notifyOpenTickets && ticket.isOpen()) {
+					notifier.queueMailing(ticket);
+				}
+			}
+			if (notifyOpenTickets) {
+				notifier.sendAll();
+			}
 			return true;
 		} catch (IOException e) {
 			log.error("failed to delete milestone " + milestone + " in " + repository, e);
 		} finally {
-			db.close();
+			if (db != null) {
+				db.close();
+			}
 		}
 		return false;
 	}
@@ -662,6 +766,7 @@
 	 *
 	 * @param repository
 	 * @return a new ticket id
+	 * @since 1.4.0
 	 */
 	public abstract long assignNewId(RepositoryModel repository);
 
@@ -671,14 +776,25 @@
 	 * @param repository
 	 * @param ticketId
 	 * @return true if the ticket exists
+	 * @since 1.4.0
 	 */
 	public abstract boolean hasTicket(RepositoryModel repository, long ticketId);
+	
+	/**
+	 * Returns all tickets.  This is not a Lucene search!
+	 * 
+	 * @return all tickets
+	 */
+	public List<TicketModel> getTickets() {
+		return getTickets(null, null);
+	}
 
 	/**
 	 * Returns all tickets.  This is not a Lucene search!
 	 *
 	 * @param repository
-	 * @return all tickets
+	 * @return all tickets of a given repository
+	 * @since 1.4.0
 	 */
 	public List<TicketModel> getTickets(RepositoryModel repository) {
 		return getTickets(repository, null);
@@ -694,6 +810,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 +820,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 +861,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 +870,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 +884,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 +896,7 @@
 	 * Returns true if attachments are supported.
 	 *
 	 * @return true if attachments are supported
+	 * @since 1.4.0
 	 */
 	public abstract boolean supportsAttachments();
 
@@ -782,6 +907,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 +919,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 +934,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 +960,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 +983,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 +1002,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 +1022,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 +1045,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 +1057,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 +1071,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 +1083,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 +1100,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 +1122,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 +1138,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 +1159,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 +1178,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 +1193,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 +1208,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 +1217,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 +1244,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 +1262,7 @@
 	 * of ticket updates, namely merging from the web ui.
 	 *
 	 * @param runnable
+	 * @since 1.4.0
 	 */
 	public synchronized void exec(Runnable runnable) {
 		runnable.run();

--
Gitblit v1.9.1