NIHVIVO-3209 Create VitroBackgroundThread to provide an answer to the question "are all background threads idle?". Make IndexBuilder and VClassGroupCache.RebuildGroupCacheThread subclasses of VitroBackgroundThread, so they will be included in that question.

This commit is contained in:
j2blake 2011-10-11 19:45:30 +00:00
parent d3f77a374c
commit e8d8c494f7
5 changed files with 242 additions and 46 deletions

View file

@ -34,6 +34,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.filtering.WebappDaoFactoryFiltering;
import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilterUtils;
import edu.cornell.mannlib.vitro.webapp.dao.filtering.filters.VitroFilters;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;
public class VClassGroupCache {
private static final Log log = LogFactory.getLog(VClassGroupCache.class);
@ -230,7 +231,7 @@ public class VClassGroupCache {
/* ******************** RebuildGroupCacheThread **************** */
protected class RebuildGroupCacheThread extends Thread {
protected class RebuildGroupCacheThread extends VitroBackgroundThread {
private final VClassGroupCache cache;
private long queueChangeMillis = 0L;
private long timeToBuildLastCache = 100L; //in msec
@ -256,8 +257,10 @@ public class VClassGroupCache {
log.debug("rebuildGroupCacheThread.run() -- starting rebuildCache()");
long start = System.currentTimeMillis();
setWorkLevel(WorkLevel.WORKING);
rebuildRequested = false;
rebuildCache( cache );
setWorkLevel(WorkLevel.IDLE);
timeToBuildLastCache = System.currentTimeMillis() - start;
log.debug("rebuildGroupCacheThread.run() -- rebuilt cache in "

View file

@ -1,10 +1,16 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
/* $This file is dactionistrietupbuted under the terms of theare license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.search.controller;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -12,58 +18,131 @@ import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.ManageSearchIndex;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevel;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevelStamp;
/**
* Accepts requests to rebuild or update the search index. It uses
* an IndexBuilder and finds that IndexBuilder from the servletContext using
* the key "edu.cornel.mannlib.vitro.search.indexing.IndexBuilder"
* Accepts requests to rebuild or update the search index. It uses an
* IndexBuilder and finds that IndexBuilder from the servletContext using the
* key "edu.cornel.mannlib.vitro.search.indexing.IndexBuilder"
*
* That IndexBuilder will be associated with a object that implements the IndexerIface.
* That IndexBuilder will be associated with a object that implements the
* IndexerIface.
*
* An example of the IndexerIface is SolrIndexer.
* An example of the IndexBuilder and SolrIndexer setup is in SolrSetup.
* An example of the IndexerIface is SolrIndexer. An example of the IndexBuilder
* and SolrIndexer setup is in SolrSetup.
*
* @author bdc34
*/
public class IndexController extends FreemarkerHttpServlet {
private static final Log log = LogFactory.getLog(IndexController.class);
public static final Actions REQUIRED_ACTIONS = new Actions(new ManageSearchIndex());
/**
* <pre>
* This request might be:
* SETUP -- Index is not building and nothing is requested. Solicit requests.
* REFRESH -- Index is building, nothing is requested. Show continuing status.
* REBUILD -- Rebuild is requested. Set the rebuild flag and show continuing status.
* UPDATE -- Update is requested. Set the update flag and show continuing status.
* </pre>
*/
private enum RequestType {
SETUP, REFRESH, REBUILD, UPDATE;
/** What type of request is this? */
static RequestType fromRequest(HttpServletRequest req) {
if (hasParameter(req, "rebuild")) {
return REBUILD;
} else if (hasParameter(req, "update")) {
return UPDATE;
} else {
ServletContext ctx = req.getSession().getServletContext();
IndexBuilder builder = IndexBuilder.getBuilder(ctx);
WorkLevelStamp workLevel = builder.getWorkLevel();
if (workLevel.getLevel() == WorkLevel.WORKING) {
return REFRESH;
} else {
return SETUP;
}
}
}
private static boolean hasParameter(HttpServletRequest req, String key) {
String value = req.getParameter(key);
return (value != null) && (!value.isEmpty());
}
}
private static final String PAGE_URL = "/SearchIndex";
private static final String TEMPLATE_NAME = "searchIndex.ftl";
public static final Actions REQUIRED_ACTIONS = new Actions(
new ManageSearchIndex());
@Override
protected Actions requiredActions(VitroRequest vreq) {
return REQUIRED_ACTIONS;
}
@Override
protected String getTitle(String siteName, VitroRequest vreq) {
return "Full Search Index Rebuild";
}
@Override
protected String getTitle(String siteName, VitroRequest vreq) {
return "Search Index Update or Rebuild";
}
@Override
protected ResponseValues processRequest(VitroRequest vreq) {
Map<String, Object> body = new HashMap<String, Object>();
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
if (RequestType.fromRequest(req) == RequestType.REFRESH) {
resp.addHeader("Refresh", "5; " + UrlBuilder.getUrl(PAGE_URL));
}
super.doGet(req, resp);
}
try {
IndexBuilder builder = (IndexBuilder)getServletContext().getAttribute(IndexBuilder.class.getName());
if( vreq.getParameter("update") != null ){
builder.doUpdateIndex();
}else{
builder.doIndexRebuild();
}
@Override
protected ResponseValues processRequest(VitroRequest vreq) {
Map<String, Object> body = new HashMap<String, Object>();
body.put("actionUrl", UrlBuilder.getUrl(PAGE_URL));
} catch (Exception e) {
log.error("Error rebuilding search index",e);
body.put("errorMessage", "There was an error while rebuilding the search index. " + e.getMessage());
return new ExceptionResponseValues(Template.ERROR_MESSAGE.toString(), body, e);
}
try {
IndexBuilder builder = IndexBuilder.getBuilder(getServletContext());
switch (RequestType.fromRequest(vreq)) {
case REBUILD:
builder.doIndexRebuild();
return redirectToRefresh(body);
case UPDATE:
builder.doUpdateIndex();
return redirectToRefresh(body);
default:
return showCurrentStatus(builder, body);
}
} catch (Exception e) {
log.error("Error rebuilding search index", e);
body.put("errorMessage",
"There was an error while rebuilding the search index. "
+ e.getMessage());
return new ExceptionResponseValues(
Template.ERROR_MESSAGE.toString(), body, e);
}
}
private ResponseValues redirectToRefresh(Map<String, Object> body) {
return new RedirectResponseValues(PAGE_URL);
}
private ResponseValues showCurrentStatus(IndexBuilder builder,
Map<String, Object> body) {
WorkLevelStamp stamp = builder.getWorkLevel();
body.put("worklevel", stamp.getLevel().toString());
body.put("since", stamp.getSince());
body.put("hasPreviousBuild", stamp.getSince().getTime() > 0L);
return new TemplateResponseValues(TEMPLATE_NAME, body);
}
body.put("message","Rebuild of search index started. A message will be written to the vivo log when indexing is complete.");
return new TemplateResponseValues(Template.MESSAGE.toString(), body);
}
}

View file

@ -25,6 +25,7 @@ import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.search.beans.IndexerIface;
import edu.cornell.mannlib.vitro.webapp.search.beans.StatementToURIsToUpdate;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;
/**
@ -37,7 +38,7 @@ import edu.cornell.mannlib.vitro.webapp.search.beans.StatementToURIsToUpdate;
* listener can use an IndexBuilder to keep the full text index in sncy with
* updates to a model. It calls IndexBuilder.addToChangedUris().
*/
public class IndexBuilder extends Thread {
public class IndexBuilder extends VitroBackgroundThread {
private WebappDaoFactory wdf;
private final IndexerIface indexer;
@ -72,6 +73,16 @@ public class IndexBuilder extends Thread {
private static final Log log = LogFactory.getLog(IndexBuilder.class);
public static IndexBuilder getBuilder(ServletContext ctx) {
Object o = ctx.getAttribute(IndexBuilder.class.getName());
if (o instanceof IndexBuilder) {
return (IndexBuilder) o;
} else {
log.error("IndexBuilder has not bee initialized.");
return null;
}
}
public IndexBuilder(IndexerIface indexer,
WebappDaoFactory wdf,
List<StatementToURIsToUpdate> stmtToURIsToIndexFunctions ){
@ -127,10 +138,6 @@ public class IndexBuilder extends Thread {
this.notifyAll();
}
public boolean isIndexing(){
return indexer.isIndexing();
}
/**
* This is called when the system shuts down.
*/
@ -145,12 +152,16 @@ public class IndexBuilder extends Thread {
while(! stopRequested ){
try{
if( reindexRequested ){
setWorkLevel(WorkLevel.WORKING);
log.debug("full re-index requested");
indexRebuild();
setWorkLevel(WorkLevel.IDLE);
}else if( !changedStmtQueue.isEmpty() ){
setWorkLevel(WorkLevel.WORKING);
Thread.sleep(WAIT_AFTER_NEW_WORK_INTERVAL); //wait a bit to let a bit more work to come into the queue
log.debug("work found for IndexBuilder, starting update");
updatedIndex();
setWorkLevel(WorkLevel.IDLE);
} else {
log.debug("there is no indexing working to do, waiting for work");
synchronized (this) { this.wait(MAX_IDLE_INTERVAL); }

View file

@ -0,0 +1,80 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.utils.threads;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A simple base class that will allow us to find the background threads and
* check their current status.
*/
public class VitroBackgroundThread extends Thread {
Log log = LogFactory.getLog(VitroBackgroundThread.class);
private static final ConcurrentLinkedQueue<WeakReference<VitroBackgroundThread>> allThreads = new ConcurrentLinkedQueue<WeakReference<VitroBackgroundThread>>();
public static List<VitroBackgroundThread> getThreads() {
List<VitroBackgroundThread> list = new ArrayList<VitroBackgroundThread>();
for (WeakReference<VitroBackgroundThread> ref : allThreads) {
VitroBackgroundThread t = ref.get();
if (t != null) {
list.add(t);
}
}
return list;
}
public enum WorkLevel {
IDLE, WORKING
}
private volatile WorkLevelStamp stamp = new WorkLevelStamp(WorkLevel.IDLE);
public VitroBackgroundThread(String name) {
super(name);
allThreads.add(new WeakReference<VitroBackgroundThread>(this));
}
public VitroBackgroundThread(Runnable target, String name) {
super(target, name);
allThreads.add(new WeakReference<VitroBackgroundThread>(this));
}
protected void setWorkLevel(WorkLevel level) {
log.debug("Set work level on '" + this.getName() + "' to " + level);
stamp = new WorkLevelStamp(level);
}
public WorkLevelStamp getWorkLevel() {
return stamp;
}
/**
* An immutable object that holds both the current work level and the time
* that it was set.
*/
public static class WorkLevelStamp {
private final WorkLevel level;
private final long since;
public WorkLevelStamp(WorkLevel level) {
this.level = level;
this.since = System.currentTimeMillis();
}
public WorkLevel getLevel() {
return level;
}
public Date getSince() {
return new Date(since);
}
}
}

View file

@ -0,0 +1,23 @@
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
<#--
Template for the page that controls the updating or rebuilding of the Search Index.
-->
<h1>Search Index Status</h1>
<#if worklevel == "IDLE">
<#if hasPreviousBuild??>
<p>Previous activity completed at ${since?string("hh:mm:ss a, MMMM dd, yyyy")}</p>
</#if>
<form action="${actionUrl}" method="POST">
<input type="submit" name="update" value="Update">
Add the latest changes to the index.
<br>
<input type="submit" name="rebuild" value="Rebuild">
Start with an empty index and build it completely.
</form>
<#else>
<p>Active since ${since?string("hh:mm:ss a, MMMM dd, yyyy")}</p>
</#if>