edu.cornell.mannlib.vitro.webapp.search.controller.IndexController.java Source code

Java tutorial

Introduction

Here is the source code for edu.cornell.mannlib.vitro.webapp.search.controller.IndexController.java

Source

/* $This file is distributed under the terms of the license in /doc/license.txt$ */

package edu.cornell.mannlib.vitro.webapp.search.controller;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

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;

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.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.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.modules.searchIndexer.SearchIndexer;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus;
import edu.cornell.mannlib.vitro.webapp.modules.searchIndexer.SearchIndexerStatus.RebuildCounts;
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 display the current status of the search index, or to
 * initiate a rebuild.
 * 
 * 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.
 * 
 * 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);

    /**
     * <pre>
     * This request might be:
     * 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 {
        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 {
                return DISPLAY;
            }
        }

        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 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 static IndexHistory history;

    @Override
    public void init() throws ServletException {
        this.indexer = ApplicationUtils.instance().getSearchIndexer();
        super.init();
    }

    /**
     * Called by SearchIndexerSetup to provide a history that dates from
     * startup, not just from servlet load time.
     */
    public static void setHistory(IndexHistory history) {
        IndexController.history = history;
    }

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        switch (RequestType.fromRequest(req)) {
        case STATUS:
            showStatus(req, resp);
            break;
        default:
            super.doGet(req, resp);
            break;
        }
    }

    @Override
    protected String getTitle(String siteName, VitroRequest vreq) {
        return "Rebuild Search Index";
    }

    @Override
    protected AuthorizationRequest requiredActions(VitroRequest vreq) {
        return REQUIRED_ACTIONS;
    }

    @Override
    protected ResponseValues processRequest(VitroRequest vreq) {
        switch (RequestType.fromRequest(vreq)) {
        case REBUILD:
            requestRebuild();
            try {
                // Pause, giving a chance to start the task queue.
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // Don't care, so pass it along.
                Thread.currentThread().interrupt();
            }
            return new RedirectResponseValues(PAGE_URL);
        default:
            return showDisplay();
        }
    }

    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 void showStatus(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        if (!PolicyHelper.isAuthorizedForActions(req, REQUIRED_ACTIONS)) {
            resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
            resp.getWriter().write("You are not authorized to access this page.");
            return;
        }

        try {
            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()));
            if (history != null) {
                body.put("history", history.toMaps());
            }

            String rendered = FreemarkerProcessingServiceSetup.getService(getServletContext())
                    .renderTemplate(STATUS_TEMPLATE_NAME, body, req);
            resp.getWriter().write(rendered);
        } catch (Exception e) {
            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 if (state == State.REBUILDING) {
            RebuildCounts counts = status.getCounts().asRebuildCounts();
            map.put("documentsBefore", counts.getDocumentsBefore());
            map.put("documentsAfter", counts.getDocumentsAfter());
        } 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();
        long elapsedMillis = now.getTime() - startTime.getTime();
        if (elapsedMillis <= 0) {
            return now;
        }
        if (completedCount <= 0) {
            return now;
        }
        if (totalToDo <= completedCount) {
            return now;
        }

        long millisPerRecord = elapsedMillis / completedCount;
        long expectedDuration = totalToDo * millisPerRecord;
        return new Date(expectedDuration + startTime.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 new int[] { (int) hours, (int) minutes, (int) seconds };
    }
}