VIVO-873 Adapt the client code to the new interface.

Create a bridge implementation of SearchIndexerImpl that just wraps around an old IndexBuilder.

Modify client code:
Application, BasicAuthenticator, SearchServiceController, SparqlUpdateApiController,
UpdateUrisInIndex and VClassGroupCache

Rewrite IndexController to use AJAX and to show the current status and history of the indexer events.
This commit is contained in:
Jim Blake 2015-01-07 16:18:41 -05:00
parent 3bc42c1456
commit 2ceab6e3df
21 changed files with 1038 additions and 204 deletions

View file

@ -16,13 +16,14 @@ import edu.cornell.mannlib.vitro.webapp.modules.ComponentStartupStatus;
import edu.cornell.mannlib.vitro.webapp.modules.fileStorage.FileStorage;
import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerModule;
import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ConfigurationTripleSource;
import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ContentTripleSource;
import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
import edu.cornell.mannlib.vitro.webapp.triplesource.impl.BasicCombinedTripleSource;
import edu.cornell.mannlib.vitro.webapp.startup.ComponentStartupStatusImpl;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.triplesource.impl.BasicCombinedTripleSource;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.Validation;
@ -38,6 +39,7 @@ public class ApplicationImpl implements Application {
private VitroHomeDirectory homeDirectory;
private SearchEngine searchEngine;
private SearchIndexer searchIndexer;
private ImageProcessor imageProcessor;
private FileStorage fileStorage;
private ContentTripleSource contentTripleSource;
@ -73,11 +75,27 @@ public class ApplicationImpl implements Application {
searchEngine = se;
} else {
throw new IllegalStateException(
"Configuration includes multiple SearchEngine instancess: "
"Configuration includes multiple SearchEngine instances: "
+ searchEngine + ", and " + se);
}
}
@Override
public SearchIndexer getSearchIndexer() {
return searchIndexer;
}
@Property(uri = "http://vitro.mannlib.cornell.edu/ns/vitro/ApplicationSetup#hasSearchIndexer")
public void setSearchIndexer(SearchIndexer si) {
if (searchIndexer == null) {
searchIndexer = si;
} else {
throw new IllegalStateException(
"Configuration includes multiple SearchIndexer instances: "
+ searchIndexer + ", and " + si);
}
}
@Override
public ImageProcessor getImageProcessor() {
return imageProcessor;
@ -89,7 +107,7 @@ public class ApplicationImpl implements Application {
imageProcessor = ip;
} else {
throw new IllegalStateException(
"Configuration includes multiple ImageProcessor instancess: "
"Configuration includes multiple ImageProcessor instances: "
+ imageProcessor + ", and " + ip);
}
}
@ -105,7 +123,7 @@ public class ApplicationImpl implements Application {
fileStorage = fs;
} else {
throw new IllegalStateException(
"Configuration includes multiple FileStorage intances: "
"Configuration includes multiple FileStorage instances: "
+ fileStorage + ", and " + fs);
}
}
@ -121,7 +139,7 @@ public class ApplicationImpl implements Application {
contentTripleSource = source;
} else {
throw new IllegalStateException(
"Configuration includes multiple intances of ContentTripleSource: "
"Configuration includes multiple instances of ContentTripleSource: "
+ contentTripleSource + ", and " + source);
}
}
@ -137,7 +155,7 @@ public class ApplicationImpl implements Application {
configurationTripleSource = source;
} else {
throw new IllegalStateException(
"Configuration includes multiple intances of ConfigurationTripleSource: "
"Configuration includes multiple instances of ConfigurationTripleSource: "
+ configurationTripleSource + ", and " + source);
}
}
@ -153,7 +171,7 @@ public class ApplicationImpl implements Application {
tboxReasonerModule = module;
} else {
throw new IllegalStateException(
"Configuration includes multiple intances of TBoxReasonerModule: "
"Configuration includes multiple instances of TBoxReasonerModule: "
+ tboxReasonerModule + ", and " + module);
}
}
@ -164,6 +182,10 @@ public class ApplicationImpl implements Application {
throw new IllegalStateException(
"Configuration did not include a SearchEngine.");
}
if (searchIndexer == null) {
throw new IllegalStateException(
"Configuration did not include a SearchIndexer.");
}
if (imageProcessor == null) {
throw new IllegalStateException(
"Configuration did not include an ImageProcessor.");

View file

@ -25,11 +25,12 @@ import com.hp.hpl.jena.update.UpdateAction;
import com.hp.hpl.jena.update.UpdateFactory;
import com.hp.hpl.jena.update.UpdateRequest;
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
/**
* Process SPARQL Updates, as an API.
@ -98,13 +99,14 @@ public class SparqlUpdateApiController extends VitroApiServlet {
ServletContext ctx = req.getSession().getServletContext();
VitroRequest vreq = new VitroRequest(req);
IndexBuilder.getBuilder(ctx).pause();
SearchIndexer indexer = ApplicationUtils.instance().getSearchIndexer();
indexer.pause();
try {
Dataset ds = new RDFServiceDataset(vreq.getUnfilteredRDFService());
GraphStore graphStore = GraphStoreFactory.create(ds);
UpdateAction.execute(parsed, graphStore);
} finally {
IndexBuilder.getBuilder(ctx).unpause();
indexer.unpause();
}
}

View file

@ -15,6 +15,7 @@ import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource;
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.IsRootUser;
import edu.cornell.mannlib.vitro.webapp.beans.BaseResourceBean.RoleLevel;
@ -29,7 +30,8 @@ import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.dao.jena.LoginEvent;
import edu.cornell.mannlib.vitro.webapp.dao.jena.LogoutEvent;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngineException;
/**
* The "standard" implementation of Authenticator.
@ -162,7 +164,17 @@ public class BasicAuthenticator extends Authenticator {
if (IsRootUser.isRootUser(RequestIdentifiers
.getIdBundleForRequest(request))) {
IndexBuilder.checkIndexOnRootLogin(request);
try {
SearchEngine engine = ApplicationUtils.instance()
.getSearchEngine();
if (engine.documentCount() == 0) {
log.info("Search index is empty. Running a full index rebuild.");
ApplicationUtils.instance().getSearchIndexer()
.rebuildIndex();
}
} catch (SearchEngineException e) {
log.warn("Unable to check for search index", e);
}
}
}
@ -262,7 +274,8 @@ public class BasicAuthenticator extends Authenticator {
* Get a reference to the UserAccountsDao, or null.
*/
private UserAccountsDao getUserAccountsDao() {
UserAccountsDao userAccountsDao = getWebappDaoFactory().getUserAccountsDao();
UserAccountsDao userAccountsDao = getWebappDaoFactory()
.getUserAccountsDao();
if (userAccountsDao == null) {
log.error("getUserAccountsDao: no UserAccountsDao");
}

View file

@ -40,9 +40,9 @@ import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchFacetField.Count;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer.Event;
import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexingEventListener;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;
@ -64,7 +64,7 @@ import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;
* search index is not built or if there were problems building the index,
* the class counts from VClassGroupCache will be incorrect.
*/
public class VClassGroupCache implements IndexingEventListener {
public class VClassGroupCache implements SearchIndexer.Listener {
private static final Log log = LogFactory.getLog(VClassGroupCache.class);
private static final String ATTRIBUTE_NAME = "VClassGroupCache";
@ -208,19 +208,17 @@ public class VClassGroupCache implements IndexingEventListener {
* Handle notification of events from the IndexBuilder.
*/
@Override
public void notifyOfIndexingEvent(EventTypes event) {
switch( event ){
case FINISH_FULL_REBUILD:
case FINISHED_UPDATE:
log.debug("rebuilding because of IndexBuilder " + event.name());
requestCacheUpdate();
break;
default:
log.debug("ignoring event type " + event.name());
break;
}
}
public void receiveSearchIndexerEvent(Event event) {
switch (event.getType()) {
case STOP_PROCESSING_URIS:
log.debug("rebuilding because of IndexBuilder " + event.getType());
requestCacheUpdate();
break;
default:
log.debug("ignoring event type " + event.getType());
break;
}
}
/* **************** static utility methods ***************** */
@ -489,8 +487,8 @@ public class VClassGroupCache implements IndexingEventListener {
context.setAttribute(ATTRIBUTE_NAME,vcgc);
log.info("VClassGroupCache added to context");
IndexBuilder indexBuilder = IndexBuilder.getBuilder(context);
indexBuilder.addIndexBuilderListener(vcgc);
SearchIndexer searchIndexer = ApplicationUtils.instance().getSearchIndexer();
searchIndexer.addListener(vcgc);
log.info("VClassGroupCache set to listen to events from IndexBuilder");
}

View file

@ -8,6 +8,7 @@ import edu.cornell.mannlib.vitro.webapp.application.VitroHomeDirectory;
import edu.cornell.mannlib.vitro.webapp.modules.fileStorage.FileStorage;
import edu.cornell.mannlib.vitro.webapp.modules.imageProcessor.ImageProcessor;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.modules.tboxreasoner.TBoxReasonerModule;
import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ConfigurationTripleSource;
import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ContentTripleSource;
@ -17,21 +18,23 @@ import edu.cornell.mannlib.vitro.webapp.modules.tripleSource.ContentTripleSource
*/
public interface Application {
ServletContext getServletContext();
VitroHomeDirectory getHomeDirectory();
SearchEngine getSearchEngine();
SearchIndexer getSearchIndexer();
ImageProcessor getImageProcessor();
FileStorage getFileStorage();
ContentTripleSource getContentTripleSource();
ConfigurationTripleSource getConfigurationTripleSource();
TBoxReasonerModule getTBoxReasonerModule();
void shutdown();
public interface Component {
@ -39,8 +42,16 @@ public interface Application {
NEW, ACTIVE, STOPPED
}
/**
* This should be called only once, and should be the first call on this
* Component.
*/
void startup(Application application, ComponentStartupStatus ss);
/**
* This should be called only once, and should be the last call on this
* Component.
*/
void shutdown(Application application);
}

View file

@ -0,0 +1,147 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.modules.searchIndexer;
import java.util.Collection;
import edu.cornell.mannlib.vitro.webapp.modules.Application;
/**
* Interface for the code that controls the contents of the search index.
*
* The only calls that are valid after shutdown are shutdown(), getStatus() and
* removeListener().
*/
public interface SearchIndexer extends Application.Module {
/**
* Update the search documents for these URIs.
*
* For each URI that belongs in the index, a new search document is built,
* replacing any document that may already exist for that URI. For each URI
* that does not belong in the index, any existing document is removed.
*
* A URI belongs in the index if it refers to an existing individual in the
* model, and is not excluded.
*
* @param uris
* if null or empty, this call has no effect.
* @throws IllegalStateException
* if called after shutdown()
*/
void scheduleUpdatesForUris(Collection<String> uris);
/**
* Remove all of the existing documents in the index, and schedule updates
* for all of the individuals in the model.
*
* If a rebuild is already pending or in progress, this method has no
* effect.
*
* @throws IllegalStateException
* if called after shutdown()
*/
void rebuildIndex();
/**
* Stop processing new tasks. Requests will be queued until a call to
* unpause().
*
* @throws IllegalStateException
* if called after shutdown()
*/
void pause();
/**
* Resume processing new tasks. Any requests that were received since the
* call to pause() will now be scheduled for processing.
*
* Has no effect if called after shutdown().
*/
void unpause();
/**
* What is the current status of the indexer?
*
* Still valid after shutdown().
*/
SearchIndexerStatus getStatus();
/**
* Add this listener, allowing it to receive events from the indexer. If
* this listener has already been added, this method has no effect.
*
* @param listener
* if null, this method has no effect.
* @throws IllegalStateException
* if called after shutdown()
*/
void addListener(Listener listener);
/**
* Remove this listener, meaning that it will no longer receive events from
* the indexer. If this listener is not active, this method has no effect.
*
* Has no effect if called after shutdown().
*
* @param listener
* if null, this method has no effect.
*/
void removeListener(Listener listener);
/**
* Stop processing and release resources. This call should block until the
* dependent threads are stopped.
*
* Repeated calls have no effect.
*/
@Override
void shutdown(Application app);
/**
* A listener that will be notified of events from the SearchIndexer.
*/
public static interface Listener {
void receiveSearchIndexerEvent(Event event);
}
/**
* An immutable event object. The event type describes just what happened.
* The status object describes what the indexer is doing now.
*/
public static class Event {
public enum Type {
START_PROCESSING_URIS,
PROGRESS_PROCESSING_URIS,
STOP_PROCESSING_URIS,
START_PROCESSING_STATEMENTS,
PROGRESS_PROCESSING_STATEMENTS,
STOP_PROCESSING_STATEMENTS,
REBUILD_REQUESTED,
SHUTDOWN_REQUESTED
}
private final Type type;
private final SearchIndexerStatus status;
public Event(Type type, SearchIndexerStatus status) {
this.type = type;
this.status = status;
}
public Type getType() {
return type;
}
public SearchIndexerStatus getStatus() {
return status;
}
}
}

View file

@ -0,0 +1,156 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.modules.searchIndexer;
import java.util.Date;
/**
* An immutable summary of the status of the SearchIndexer, at some point in
* time.Contains the current state, and some counts.
*
* If the indexer is processing URIs, processing statements, or preparing a
* rebuild, the counts are URI_COUNTS, STATEMENT_COUNTS, or REBUILD_COUNTS.
*
* When the indexer starts up, and when it is is shut down, the counts are
* NO_COUNTS.
*
* If the indexer is idle, the counts are carried over from the previous
* operation.
*/
public class SearchIndexerStatus {
public enum State {
IDLE, PROCESSING_URIS, PROCESSING_STMTS, PREPARING_REBUILD, SHUTDOWN
}
private final State state;
private final Date since;
private final Counts counts;
public SearchIndexerStatus(State state, Date since, Counts counts) {
this.state = state;
this.since = since;
this.counts = counts;
}
public State getState() {
return state;
}
public Date getSince() {
return since;
}
public Counts getCounts() {
return counts;
}
public abstract static class Counts {
public enum Type {
URI_COUNTS, STATEMENT_COUNTS, REBUILD_COUNTS, NO_COUNTS
}
private final Type type;
public Counts(Type type) {
this.type = type;
}
public Type getType() {
return this.type;
}
public UriCounts asUriCounts() {
return (UriCounts) this;
}
public StatementCounts asStatementCounts() {
return (StatementCounts) this;
}
public RebuildCounts asRebuildCounts() {
return (RebuildCounts) this;
}
public NoCounts asNoCounts() {
return (NoCounts) this;
}
}
public static class UriCounts extends Counts {
private final int deleted;
private final int updated;
private final int remaining;
private final int total;
public UriCounts(int deleted, int updated, int remaining, int total) {
super(Type.URI_COUNTS);
this.deleted = deleted;
this.updated = updated;
this.remaining = remaining;
this.total = total;
}
public int getDeleted() {
return deleted;
}
public int getUpdated() {
return updated;
}
public int getRemaining() {
return remaining;
}
public int getTotal() {
return total;
}
}
public static class StatementCounts extends Counts {
private final int processed;
private final int remaining;
private final int total;
public StatementCounts(int processed, int remaining, int total) {
super(Type.STATEMENT_COUNTS);
this.processed = processed;
this.remaining = remaining;
this.total = total;
}
public int getProcessed() {
return processed;
}
public int getRemaining() {
return remaining;
}
public int getTotal() {
return total;
}
}
public static class RebuildCounts extends Counts {
private final int numberOfIndividuals;
public RebuildCounts(int numberOfIndividuals) {
super(Type.REBUILD_COUNTS);
this.numberOfIndividuals = numberOfIndividuals;
}
public int getNumberOfIndividuals() {
return numberOfIndividuals;
}
}
public static class NoCounts extends Counts {
public NoCounts() {
super(Type.NO_COUNTS);
}
}
}

View file

@ -3,12 +3,10 @@
package edu.cornell.mannlib.vitro.webapp.search.controller;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
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;
@ -18,31 +16,30 @@ import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.RequestedAction;
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;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.State;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.StatementCounts;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.UriCounts;
import edu.cornell.mannlib.vitro.webapp.services.freemarker.FreemarkerProcessingServiceSetup;
/**
* 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 display the current status of the search index, or to
* initiate a rebuild.
*
* That IndexBuilder will be associated with a object that implements the
* IndexerIface.
* A DISPLAY or REBUILD request is handled like any other FreemarkerHttpServlet.
* A STATUS is an AJAX request, we override doGet() so we can format the
* template without enclosing it in a body template.
*
* An example of the IndexerIface is SearchIndexer. An example of the IndexBuilder
* and SearchIndexer setup is in SearchIndexerSetup.
*
* @author bdc34
* When initialized, this servlet adds a listener to the SearchIndexer, so it
* can maintain a history of activity. This will provide the contents of the
* display.
*/
public class IndexController extends FreemarkerHttpServlet {
private static final Log log = LogFactory.getLog(IndexController.class);
@ -50,27 +47,22 @@ public class IndexController extends FreemarkerHttpServlet {
/**
* <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.
* DISPLAY (default) -- Send the template that will contain the status display.
* STATUS -- Send the current status and history.
* REBUILD -- Initiate a rebuild. Then act like DISPLAY.
* </pre>
*/
private enum RequestType {
SETUP, REFRESH, REBUILD;
DISPLAY, STATUS, REBUILD;
/** What type of request is this? */
static RequestType fromRequest(HttpServletRequest req) {
if (hasParameter(req, "rebuild")) {
return REBUILD;
} else if (hasParameter(req, "status")) {
return STATUS;
} 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;
}
return DISPLAY;
}
}
@ -81,13 +73,43 @@ public class IndexController extends FreemarkerHttpServlet {
}
private static final String PAGE_URL = "/SearchIndex";
private static final String TEMPLATE_NAME = "searchIndex.ftl";
private static final String PAGE_TEMPLATE_NAME = "searchIndex.ftl";
private static final String STATUS_TEMPLATE_NAME = "searchIndexStatus.ftl";
public static final RequestedAction REQUIRED_ACTIONS = SimplePermission.MANAGE_SEARCH_INDEX.ACTION;
private SearchIndexer indexer;
private IndexHistory history;
@Override
protected AuthorizationRequest requiredActions(VitroRequest vreq) {
return REQUIRED_ACTIONS;
public void init() throws ServletException {
super.init();
this.indexer = ApplicationUtils.instance().getSearchIndexer();
this.history = new IndexHistory();
this.indexer.addListener(this.history);
}
@Override
public void destroy() {
this.indexer.removeListener(this.history);
super.destroy();
}
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
if (!isAuthorizedToDisplayPage(req, resp, REQUIRED_ACTIONS)) {
return;
}
switch (RequestType.fromRequest(req)) {
case STATUS:
showStatus(req, resp);
break;
default:
super.doGet(req, resp);
break;
}
}
@Override
@ -95,78 +117,82 @@ public class IndexController extends FreemarkerHttpServlet {
return "Rebuild Search Index";
}
@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);
}
@Override
protected ResponseValues processRequest(VitroRequest vreq) {
Map<String, Object> body = new HashMap<String, Object>();
body.put("actionUrl", UrlBuilder.getUrl(PAGE_URL));
try {
IndexBuilder builder = IndexBuilder.getBuilder(getServletContext());
switch (RequestType.fromRequest(vreq)) {
case REBUILD:
builder.doIndexRebuild();
Thread.sleep(500);
return redirectToRefresh();
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);
switch (RequestType.fromRequest(vreq)) {
case REBUILD:
requestRebuild();
return showDisplay();
default:
return showDisplay();
}
}
private ResponseValues redirectToRefresh() {
return new RedirectResponseValues(PAGE_URL);
private ResponseValues showDisplay() {
HashMap<String, Object> body = new HashMap<>();
body.put("statusUrl", UrlBuilder.getUrl(PAGE_URL, "status", "true"));
body.put("rebuildUrl", UrlBuilder.getUrl(PAGE_URL, "rebuild", "true"));
return new TemplateResponseValues(PAGE_TEMPLATE_NAME, body);
}
private ResponseValues showCurrentStatus(IndexBuilder builder,
Map<String, Object> body) {
WorkLevelStamp stamp = builder.getWorkLevel();
WorkLevel workLevel = stamp.getLevel();
long completedCount = builder.getCompletedCount();
long totalToDo = builder.getTotalToDo();
Date since = stamp.getSince();
Date expectedCompletion = figureExpectedCompletion(since, totalToDo,
completedCount);
body.put("worklevel", workLevel.toString());
body.put("completedCount", completedCount);
body.put("totalToDo", totalToDo);
body.put("currentTask", figureCurrentTask(stamp.getFlags()));
body.put("since", since);
body.put("elapsed", formatElapsedTime(since, new Date()));
body.put("expected", formatElapsedTime(since, expectedCompletion));
body.put("hasPreviousBuild", since.getTime() > 0L);
body.put("indexIsConnected", testIndexConnection());
return new TemplateResponseValues(TEMPLATE_NAME, body);
}
private Boolean testIndexConnection() {
private void showStatus(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
try {
ApplicationUtils.instance().getSearchEngine().ping();
return Boolean.TRUE;
Map<String, Object> body = new HashMap<>();
body.put("statusUrl", UrlBuilder.getUrl(PAGE_URL, "status", "true"));
body.put("rebuildUrl",
UrlBuilder.getUrl(PAGE_URL, "rebuild", "true"));
body.put("status", buildStatusMap(indexer.getStatus()));
body.put("history", history.toMaps());
String rendered = FreemarkerProcessingServiceSetup.getService(
getServletContext()).renderTemplate(STATUS_TEMPLATE_NAME,
body, req);
resp.getWriter().write(rendered);
} catch (Exception e) {
log.error("Can't connect to the search engine.", e);
return Boolean.FALSE;
resp.setStatus(500);
resp.getWriter().write(e.toString());
log.error(e, e);
}
}
private void requestRebuild() {
indexer.rebuildIndex();
}
private Map<String, Object> buildStatusMap(SearchIndexerStatus status) {
Map<String, Object> map = new HashMap<>();
State state = status.getState();
map.put("statusType", state);
map.put("since", status.getSince());
if (state == State.PROCESSING_URIS) {
UriCounts counts = status.getCounts().asUriCounts();
map.put("updated", counts.getUpdated());
map.put("deleted", counts.getDeleted());
map.put("remaining", counts.getRemaining());
map.put("total", counts.getTotal());
map.put("elapsed", breakDownElapsedTime(status.getSince()));
map.put("expectedCompletion",
figureExpectedCompletion(status.getSince(),
counts.getTotal(),
counts.getTotal() - counts.getRemaining()));
} else if (state == State.PROCESSING_STMTS) {
StatementCounts counts = status.getCounts().asStatementCounts();
map.put("processed", counts.getProcessed());
map.put("remaining", counts.getRemaining());
map.put("total", counts.getTotal());
map.put("elapsed", breakDownElapsedTime(status.getSince()));
map.put("expectedCompletion",
figureExpectedCompletion(status.getSince(),
counts.getTotal(), counts.getProcessed()));
} else {
// nothing for IDLE or SHUTDOWN, except what's already there.
}
return map;
}
private Date figureExpectedCompletion(Date startTime, long totalToDo,
long completedCount) {
Date now = new Date();
@ -186,22 +212,12 @@ public class IndexController extends FreemarkerHttpServlet {
return new Date(expectedDuration + startTime.getTime());
}
private String formatElapsedTime(Date since, Date until) {
long elapsedMillis = until.getTime() - since.getTime();
private int[] breakDownElapsedTime(Date since) {
long elapsedMillis = new Date().getTime() - since.getTime();
long seconds = (elapsedMillis / 1000L) % 60L;
long minutes = (elapsedMillis / 60000L) % 60L;
long hours = elapsedMillis / 3600000L;
return String.format("%02d:%02d:%02d", hours, minutes, seconds);
}
private String figureCurrentTask(Collection<String> flags) {
if (flags.contains(IndexBuilder.FLAG_REBUILDING)) {
return "Rebuilding";
} else if (flags.contains(IndexBuilder.FLAG_UPDATING)) {
return "Updating";
} else {
return "Not working on";
}
return new int[] {(int) hours, (int) minutes, (int) seconds};
}
}

View file

@ -0,0 +1,95 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.search.controller;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer.Event;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.Counts;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.Counts.Type;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.RebuildCounts;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.StatementCounts;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.UriCounts;
/**
* This listener keeps a list of the last several SearchIndexer events, and will
* format them for display in a Freemarker template.
*/
public class IndexHistory implements SearchIndexer.Listener {
private final static int MAX_EVENTS = 10;
private final Deque<Event> events = new LinkedList<>();
@Override
public void receiveSearchIndexerEvent(Event event) {
synchronized (events) {
events.addFirst(event);
while (events.size() > MAX_EVENTS) {
events.removeLast();
}
}
}
public List<Map<String, Object>> toMaps() {
synchronized (events) {
List<Map<String, Object>> list = new ArrayList<>();
for (Event event : events) {
list.add(toMap(event));
}
return list;
}
}
private Map<String, Object> toMap(Event event) {
SearchIndexerStatus status = event.getStatus();
Counts counts = status.getCounts();
Type countsType = counts.getType();
Map<String, Object> map = new HashMap<>();
map.put("event", event.getType());
map.put("statusType", status.getState());
map.put("since", status.getSince());
map.put("countsType", countsType);
switch (countsType) {
case URI_COUNTS:
addCounts(counts.asUriCounts(), map);
break;
case STATEMENT_COUNTS:
addCounts(counts.asStatementCounts(), map);
break;
case REBUILD_COUNTS:
addCounts(counts.asRebuildCounts(), map);
break;
default: // NO_COUNTS
break;
}
return map;
}
private void addCounts(UriCounts counts, Map<String, Object> map) {
map.put("updated", counts.getUpdated());
map.put("deleted", counts.getDeleted());
map.put("remaining", counts.getRemaining());
map.put("total", counts.getTotal());
}
private void addCounts(StatementCounts counts, Map<String, Object> map) {
map.put("processed", counts.getProcessed());
map.put("remaining", counts.getRemaining());
map.put("total", counts.getTotal());
}
private void addCounts(RebuildCounts counts, Map<String, Object> map) {
map.put("numberOfIndividuals", counts.getNumberOfIndividuals());
}
}

View file

@ -15,6 +15,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
@ -23,7 +24,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServ
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues;
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.modules.searchIndexer.SearchIndexer;
/**
* Accepts requests to update a set of URIs in the search index.
@ -73,8 +74,8 @@ public class SearchServiceController extends FreemarkerHttpServlet {
*/
private ResponseValues doUpdateUrisInSearch(HttpServletRequest req)
throws IOException, ServletException {
IndexBuilder builder = IndexBuilder.getBuilder(getServletContext());
int uriCount = new UpdateUrisInIndex().doUpdateUris(req, builder);
SearchIndexer indexer = ApplicationUtils.instance().getSearchIndexer();
int uriCount = new UpdateUrisInIndex().doUpdateUris(req, indexer);
Map<String, Object> body = new HashMap<>();
body.put("msg", "Received " + uriCount + " URIs.");

View file

@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
@ -20,7 +21,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
/**
* Class that performs the update of the uris in the search index for the
@ -38,7 +39,7 @@ public class UpdateUrisInIndex {
*
* @throws IOException
*/
protected int doUpdateUris(HttpServletRequest req, IndexBuilder builder)
protected int doUpdateUris(HttpServletRequest req, SearchIndexer indexer)
throws ServletException, IOException {
Map<String, List<FileItem>> map = new VitroRequest(req).getFiles();
if (map == null) {
@ -51,25 +52,25 @@ public class UpdateUrisInIndex {
for (String name : map.keySet()) {
for (FileItem item : map.get(name)) {
log.debug("Found " + item.getSize() + " byte file for '" + name + "'");
uriCount += processFileItem(builder, item, enc);
uriCount += processFileItem(indexer, item, enc);
}
}
return uriCount;
}
private int processFileItem(IndexBuilder builder,
private int processFileItem(SearchIndexer indexer,
FileItem item, Charset enc) throws IOException {
int count = 0;
List<String> uris = new ArrayList<>();
Reader reader = new InputStreamReader(item.getInputStream(), enc.name());
try (Scanner scanner = createScanner(reader)) {
while (scanner.hasNext()) {
String uri = scanner.next();
log.debug("Request to index uri '" + uri + "'");
builder.addToChanged(uri);
count++;
uris.add(uri);
}
}
return count;
indexer.scheduleUpdatesForUris(uris);
return uris.size();
}
@SuppressWarnings("resource")

View file

@ -0,0 +1,220 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.searchindex;
import static edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer.Event.Type.REBUILD_REQUESTED;
import static edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer.Event.Type.START_PROCESSING_STATEMENTS;
import static edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer.Event.Type.STOP_PROCESSING_STATEMENTS;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.modules.Application;
import edu.cornell.mannlib.vitro.webapp.modules.ComponentStartupStatus;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.State;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexingEventListener;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevel;
import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread.WorkLevelStamp;
/**
* TODO
* A silly implementation that just wraps the old IndexBuilder.
*/
public class SearchIndexerImpl implements SearchIndexer {
private static final Log log = LogFactory.getLog(SearchIndexerImpl.class);
private final ListenerList listeners = new ListenerList();
private ServletContext ctx;
// private Set<SearchIndexExcluder> excluders;
// private Set<DocumentModifier> modifiers;
// private Set<IndexingUriFinder> uriFinders;
// TODO
private IndexBuilder indexBuilder;
@Override
public void startup(Application application, ComponentStartupStatus ss) {
try {
this.ctx = application.getServletContext();
// loadConfiguration();
// ss.info("Configured SearchIndexer: excluders=" + excluders
// + ", modifiers=" + modifiers + ", uriFinders=" + uriFinders);
// TODO
this.indexBuilder = (IndexBuilder) ctx
.getAttribute(IndexBuilder.class.getName());
this.indexBuilder.addIndexBuilderListener(new BridgeListener());
} catch (Exception e) {
ss.fatal("Failed to configure the SearchIndexer", e);
}
}
private void createAndFire(Event.Type type) {
listeners.fireEvent(new Event(type, getStatus()));
}
// private void loadConfiguration() throws ConfigurationBeanLoaderException
// {
// ConfigurationBeanLoader beanLoader = new ConfigurationBeanLoader(
// ModelAccess.on(ctx).getOntModel(DISPLAY), ctx);
// excluders = beanLoader.loadAll(SearchIndexExcluder.class);
// modifiers = beanLoader.loadAll(DocumentModifier.class);
// uriFinders = beanLoader.loadAll(IndexingUriFinder.class);
// }
/*
* (non-Javadoc)
*
* @see
* edu.cornell.mannlib.vitro.webapp.modules.Application.Component#shutdown
* (edu.cornell.mannlib.vitro.webapp.modules.Application)
*/
@Override
public void shutdown(Application application) {
// TODO
}
/*
* (non-Javadoc)
*
* @see
* edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer#
* scheduleUpdatesForUris(java.util.Collection)
*/
@Override
public void scheduleUpdatesForUris(Collection<String> uris) {
// TODO
for (String uri : uris) {
indexBuilder.addToChanged(uri);
}
}
/*
* (non-Javadoc)
*
* @see
* edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer#
* rebuildIndex()
*/
@Override
public void rebuildIndex() {
// TODO
indexBuilder.doIndexRebuild();
}
/*
* (non-Javadoc)
*
* @see
* edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer#
* pause()
*/
@Override
public void pause() {
// TODO
indexBuilder.pause();
}
/*
* (non-Javadoc)
*
* @see
* edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer#
* unpause()
*/
@Override
public void unpause() {
// TODO
indexBuilder.unpause();
}
/*
* (non-Javadoc)
*
* @see
* edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexer#
* getStatus()
*/
@Override
public SearchIndexerStatus getStatus() {
// TODO
WorkLevelStamp workLevel = indexBuilder.getWorkLevel();
WorkLevel level = workLevel.getLevel();
Date since = workLevel.getSince();
if (level == WorkLevel.IDLE) {
return new SearchIndexerStatus(State.IDLE, since,
new SearchIndexerStatus.NoCounts());
} else {
return new SearchIndexerStatus(State.PROCESSING_URIS, since,
new SearchIndexerStatus.UriCounts(1, 2, 3, 6));
}
}
@Override
public void addListener(Listener listener) {
listeners.add(listener);
}
@Override
public void removeListener(Listener listener) {
listeners.remove(listener);
}
private static class ListenerList {
private final List<Listener> list;
public ListenerList() {
list = Collections.synchronizedList(new ArrayList<Listener>());
}
public void add(Listener l) {
list.add(l);
}
public void remove(Listener l) {
list.remove(l);
}
public void fireEvent(Event e) {
synchronized (list) {
for (Listener l : list) {
l.receiveSearchIndexerEvent(e);
}
}
}
}
private class BridgeListener implements IndexingEventListener {
@Override
public void notifyOfIndexingEvent(EventTypes ie) {
switch (ie) {
case START_UPDATE:
createAndFire(START_PROCESSING_STATEMENTS);
break;
case FINISHED_UPDATE:
createAndFire(STOP_PROCESSING_STATEMENTS);
break;
case START_FULL_REBUILD:
createAndFire(REBUILD_REQUESTED);
createAndFire(START_PROCESSING_STATEMENTS);
break;
default: // FINISH_FULL_REBUILD
createAndFire(STOP_PROCESSING_STATEMENTS);
break;
}
}
}
}

View file

@ -35,6 +35,7 @@ import edu.cornell.mannlib.vitro.webapp.searchindex.documentBuilding.DocumentMod
import edu.cornell.mannlib.vitro.webapp.searchindex.exclusions.SearchIndexExcluder;
import edu.cornell.mannlib.vitro.webapp.searchindex.indexing.AdditionalUriFinders;
import edu.cornell.mannlib.vitro.webapp.searchindex.indexing.StatementToURIsToUpdate;
import edu.cornell.mannlib.vitro.webapp.startup.ComponentStartupStatusImpl;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader;
import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoaderException;
@ -43,6 +44,7 @@ import edu.cornell.mannlib.vitro.webapp.utils.developer.listeners.DeveloperDisab
/**
* TODO
* A silly implementation that just wraps the old IndexBuilder with a new SearchIndexerImpl.
*/
public class SearchIndexerSetup implements ServletContextListener {
private static final Log log = LogFactory.getLog(SearchIndexerSetup.class);
@ -96,6 +98,8 @@ public class SearchIndexerSetup implements ServletContextListener {
Key.SEARCH_INDEX_SUPPRESS_MODEL_CHANGE_LISTENER));
ss.info(this, "Setup of search indexer completed.");
ApplicationUtils.instance().getSearchIndexer().startup(ApplicationUtils.instance(), new ComponentStartupStatusImpl(this, ss));
} catch (Throwable e) {
ss.fatal(this, "could not setup search engine", e);
}