// SingleThreadModel causes servlet instances to be assigned to only a // single thread (request) at a time. public abstract class LockssServlet extends HttpServlet implements SingleThreadModel { protected static Logger log = Logger.getLogger("LockssServlet"); // Constants static final String PARAM_LOCAL_IP = Configuration.PREFIX + "localIPAddress"; static final String PARAM_PLATFORM_VERSION = Configuration.PREFIX + "platform.version"; /** Inactive HTTP session (cookie) timeout */ static final String PARAM_UI_SESSION_TIMEOUT = Configuration.PREFIX + "ui.sessionTimeout"; static final long DEFAULT_UI_SESSION_TIMEOUT = 2 * Constants.DAY; /** Maximum size of uploaded file accepted */ static final String PARAM_MAX_UPLOAD_FILE_SIZE = Configuration.PREFIX + "ui.maxUploadFileSize"; static final int DEFAULT_MAX_UPLOAD_FILE_SIZE = 500000; /** The warning string to display when the UI is disabled. */ static final String PARAM_UI_WARNING = Configuration.PREFIX + "ui.warning"; // session keys static final String SESSION_KEY_OBJECT_ID = "obj_id"; static final String SESSION_KEY_OBJ_MAP = "obj_map"; public static final String SESSION_KEY_RUNNING_SERVLET = "running_servlet"; public static final String SESSION_KEY_REQUEST_HOST = "request_host"; // Name given to form element whose value is the action that should be // performed when the form is submitted. (Not always the submit button.) public static final String ACTION_TAG = "lockssAction"; public static final String JAVASCRIPT_RESOURCE = "org/lockss/htdocs/admin.js"; public static final String ATTR_INCLUDE_SCRIPT = "IncludeScript"; public static final String ATTR_ALLOW_ROLES = "AllowRoles"; /** User may configure admin access (add/delete/modify users, set admin access list) */ public static final String ROLE_USER_ADMIN = "userAdminRole"; /** User may configure content access (set content access list) */ public static final String ROLE_CONTENT_ADMIN = "contentAdminRole"; /** User may change AU configuration (add/delete content) */ public static final String ROLE_AU_ADMIN = "auAdminRole"; public static final String ROLE_DEBUG = "debugRole"; protected ServletContext context; private LockssApp theApp = null; private ServletManager servletMgr; private AccountManager acctMgr; // Request-local storage. Convenient, but requires servlet instances // to be single threaded, and must ensure reset them to avoid carrying // over state between requests. protected HttpServletRequest req; protected HttpServletResponse resp; protected URL reqURL; protected HttpSession session; private String adminDir = null; protected String clientAddr; // client addr, even if no param protected String localAddr; protected MultiPartRequest multiReq; private Vector footnotes; private int footNumber; private int tabindex; ServletDescr _myServletDescr = null; private String myName = null; // number submit buttons sequentially so unit tests can find them protected int submitButtonNumber = 0; /** Run once when servlet loaded. */ public void init(ServletConfig config) throws ServletException { super.init(config); context = config.getServletContext(); theApp = (LockssApp) context.getAttribute(ServletManager.CONTEXT_ATTR_LOCKSS_APP); servletMgr = (ServletManager) context.getAttribute(ServletManager.CONTEXT_ATTR_SERVLET_MGR); if (theApp instanceof LockssDaemon) { acctMgr = getLockssDaemon().getAccountManager(); } } public ServletManager getServletManager() { return servletMgr; } protected ServletDescr[] getServletDescrs() { return servletMgr.getServletDescrs(); } /** Servlets must implement this method. */ protected abstract void lockssHandleRequest() throws ServletException, IOException; /** Common request handling. */ public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resetState(); boolean success = false; HttpSession session = req.getSession(false); try { this.req = req; this.resp = resp; if (log.isDebug()) { logParams(); } resp.setContentType("text/html"); if (!mayPageBeCached()) { resp.setHeader("pragma", "no-cache"); resp.setHeader("Cache-control", "no-cache"); } reqURL = new URL(UrlUtil.getRequestURL(req)); clientAddr = getLocalIPAddr(); // check that current user has permission to run this servlet if (!isServletAllowed(myServletDescr())) { displayWarningInLieuOfPage("You are not authorized to use " + myServletDescr().heading); return; } // check whether servlet is disabled String reason = ServletUtil.servletDisabledReason(myServletDescr().getServletName()); if (reason != null) { displayWarningInLieuOfPage("This function is disabled. " + reason); return; } if (session != null) { session.setAttribute(SESSION_KEY_RUNNING_SERVLET, getHeading()); String reqHost = req.getRemoteHost(); String forw = req.getHeader(HttpFields.__XForwardedFor); if (!StringUtil.isNullString(forw)) { reqHost += " (proxies for " + forw + ")"; } session.setAttribute(SESSION_KEY_REQUEST_HOST, reqHost); } lockssHandleRequest(); success = (errMsg == null); } catch (ServletException e) { log.error("Servlet threw", e); throw e; } catch (IOException e) { log.error("Servlet threw", e); throw e; } catch (RuntimeException e) { log.error("Servlet threw", e); throw e; } finally { if (session != null) { session.setAttribute(SESSION_KEY_RUNNING_SERVLET, null); session.setAttribute(LockssFormAuthenticator.__J_AUTH_ACTIVITY, TimeBase.nowMs()); } if ("please".equalsIgnoreCase(req.getHeader("X-Lockss-Result"))) { log.debug3("X-Lockss-Result: " + (success ? "Ok" : "Fail")); resp.setHeader("X-Lockss-Result", success ? "Ok" : "Fail"); } resetMyLocals(); resetLocals(); } } protected void resetState() { multiReq = null; footNumber = 0; submitButtonNumber = 0; tabindex = 1; statusMsg = null; errMsg = null; isFramed = false; } protected void resetLocals() {} protected void resetMyLocals() { // Don't hold on to stuff forever req = null; resp = null; session = null; reqURL = null; adminDir = null; localAddr = null; footnotes = null; _myServletDescr = null; myName = null; multiReq = null; } /** * Return true if generated page may be cached (e.g., by browser). Default is false as most * servlets generate dynamic results */ protected boolean mayPageBeCached() { return false; } /** Set the session timeout to the configured value */ protected void setSessionTimeout(HttpSession session) { Configuration config = CurrentConfig.getCurrentConfig(); setSessionTimeout( session, config.getTimeInterval(PARAM_UI_SESSION_TIMEOUT, DEFAULT_UI_SESSION_TIMEOUT)); } /** Set the session timeout */ protected void setSessionTimeout(HttpSession session, long time) { session.setMaxInactiveInterval((int) (time / Constants.SECOND)); } /** Get the current session, creating it if necessary (and set the timeout if so) */ protected HttpSession getSession() { if (session == null) { session = req.getSession(true); if (session.isNew()) { setSessionTimeout(session); } } return session; } /** Return true iff a session has already been established */ protected boolean hasSession() { return req.getSession(false) != null; } /** Get an unused ID string for storing an object in the session */ protected String getNewSessionObjectId() { HttpSession session = getSession(); synchronized (session) { Integer id = (Integer) getSession().getAttribute(SESSION_KEY_OBJECT_ID); if (id == null) { id = new Integer(1); } session.setAttribute(SESSION_KEY_OBJECT_ID, new Integer(id.intValue() + 1)); return id.toString(); } } /** Get the object associated with the ID in the session */ protected Object getSessionIdObject(String id) { HttpSession session = getSession(); synchronized (session) { BidiMap map = (BidiMap) session.getAttribute(SESSION_KEY_OBJ_MAP); if (map == null) { return null; } return map.getKey(id); } } /** Get the String associated with the ID in the session */ protected String getSessionIdString(String id) { return (String) getSessionIdObject(id); } /** Get the ID with which the object is associated with the session, if any */ protected String getSessionObjectId(Object obj) { HttpSession session = getSession(); BidiMap map; synchronized (session) { map = (BidiMap) session.getAttribute(SESSION_KEY_OBJ_MAP); if (map == null) { map = new DualHashBidiMap(); session.setAttribute(SESSION_KEY_OBJ_MAP, map); } } synchronized (map) { String id = (String) map.get(obj); if (id == null) { id = getNewSessionObjectId(); map.put(obj, id); } return id; } } // Return descriptor of running servlet protected ServletDescr myServletDescr() { if (_myServletDescr == null) { _myServletDescr = servletMgr.findServletDescr(this); } return _myServletDescr; } // By default, servlet heading is in descr. Override method to // compute other heading protected String getHeading(ServletDescr d) { if (d == null) return "Unknown Servlet"; return d.heading; } protected String getHeading() { return getHeading(myServletDescr()); } String getLocalIPAddr() { if (localAddr == null) { try { IPAddr localHost = IPAddr.getLocalHost(); localAddr = localHost.getHostAddress(); } catch (UnknownHostException e) { // shouldn't happen log.error("LockssServlet: getLocalHost: " + e.toString()); return "???"; } } return localAddr; } // Return IP addr used by LCAP. If specified by (misleadingly named) // localIPAddress prop, might not really be our address (if we are // behind NAT). String getLcapIPAddr() { String ip = CurrentConfig.getParam(PARAM_LOCAL_IP); if (ip == null || ip.length() <= 0) { return getLocalIPAddr(); } return ip; } String getRequestHost() { return reqURL.getHost(); } String getMachineName() { return PlatformUtil.getLocalHostname(); } // String getMachineName0() { // if (myName == null) { // // Return the canonical name of the interface the request was aimed // // at. (localIPAddress prop isn't necessarily right here, as it // // might be the address of a NAT that we're behind.) // String host = reqURL.getHost(); // try { // IPAddr localHost = IPAddr.getByName(host); // String ip = localHost.getHostAddress(); // myName = getMachineName(ip); // } catch (UnknownHostException e) { // // shouldn't happen // log.error("getMachineName", e); // return host; // } // } // return myName; // } // String getMachineName(String ip) { // try { // IPAddr inet = IPAddr.getByName(ip); // return inet.getHostName(); // } catch (UnknownHostException e) { // log.warning("getMachineName", e); // } // return ip; // } // return IP given name or IP String getMachineIP(String name) { try { IPAddr inet = IPAddr.getByName(name); return inet.getHostAddress(); } catch (UnknownHostException e) { return null; } } boolean isServletLinkInNav(ServletDescr d) { return !isThisServlet(d) || linkMeInNav(); } boolean isThisServlet(ServletDescr d) { return d == myServletDescr(); } /** servlets may override this to determine whether they should be a link in nav table */ protected boolean linkMeInNav() { return false; } boolean isLargeLogo() { return myServletDescr().isLargeLogo(); } // user predicates String getUsername() { Principal user = req.getUserPrincipal(); return user != null ? user.toString() : null; } protected UserAccount getUserAccount() { if (acctMgr != null) { return acctMgr.getUser(getUsername()); } return AccountManager.NOBODY_ACCOUNT; } protected boolean isDebugUser() { return doesUserHaveRole(ROLE_DEBUG); } protected boolean doesUserHaveRole(String role) { if ((req.isUserInRole(role) || req.isUserInRole(ROLE_USER_ADMIN)) && !hasNoRoleParsm(role)) { return true; } return hasTestRole(role); } static Map<String, String> noRoleParams = new HashMap<String, String>(); static { noRoleParams.put(ROLE_USER_ADMIN, "noadmin"); noRoleParams.put(ROLE_CONTENT_ADMIN, "nocontent"); noRoleParams.put(ROLE_AU_ADMIN, "noau"); noRoleParams.put(ROLE_DEBUG, "nodebug"); } protected boolean hasNoRoleParsm(String roleName) { String noRoleParam = noRoleParams.get(roleName); return (noRoleParam != null && !StringUtil.isNullString(req.getParameter(noRoleParam))); } protected boolean hasTestRole(String role) { // Servlet test harness puts roles in context List roles = (List) context.getAttribute(ATTR_ALLOW_ROLES); return roles != null && (roles.contains(role) || roles.contains(ROLE_USER_ADMIN)); } protected boolean isServletAllowed(ServletDescr d) { if (d.needsUserAdminRole() && !doesUserHaveRole(ROLE_USER_ADMIN)) return false; if (d.needsContentAdminRole() && !doesUserHaveRole(ROLE_CONTENT_ADMIN)) return false; if (d.needsAuAdminRole() && !doesUserHaveRole(ROLE_AU_ADMIN)) return false; return d.isEnabled(getLockssDaemon()); } protected boolean isServletDisplayed(ServletDescr d) { if (!isServletAllowed(d)) return false; if (d.needsDebugRole() && !doesUserHaveRole(ROLE_DEBUG)) return false; return true; } protected boolean isServletInNav(ServletDescr d) { if (d.cls == ServletDescr.UNAVAILABLE_SERVLET_MARKER) return false; return d.isInNav(this) && isServletDisplayed(d); } // Called when a servlet doesn't get the parameters it expects/needs protected void paramError() throws IOException { // FIXME: As of 2006-03-15 this method and its only caller checkParam() are not called from // anywhere PrintWriter wrtr = resp.getWriter(); Page page = new Page(); // add referer, params, msg to contact lockss unless from old bookmark // or manually entered url page.add("Parameter error"); page.write(wrtr); } // return true iff error protected boolean checkParam(boolean ok, String msg) throws IOException { if (ok) return false; log.error(myServletDescr().getPath() + ": " + msg); paramError(); return true; } /** Construct servlet URL */ String srvURL(ServletDescr d) { return srvURL((String) null, d, null); } /** Construct servlet URL with params */ String srvURL(ServletDescr d, String params) { return srvURL((String) null, d, params); } /** Construct servlet URL with params */ String srvURL(ServletDescr d, Properties params) { return srvURL(d, concatParams(params)); } /** Construct servlet absolute URL, with params as necessary. */ String srvAbsURL(ServletDescr d, String params) { return srvURL(getRequestHost(), d, params); } /** * Construct servlet URL, with params as necessary. Avoid generating a hostname different from * that used in the original request, or browsers will prompt again for login */ String srvURL(String host, ServletDescr d, String params) { return srvURLFromStem(srvUrlStem(host), d, params); } String srvURL(PeerIdentity peer, ServletDescr d, String params) { return srvURLFromStem(peer.getUiUrlStem(reqURL.getPort()), d, params); } /** * Construct servlet URL, with params as necessary. Avoid generating a hostname different from * that used in the original request, or browsers will prompt again for login */ String srvURLFromStem(String stem, ServletDescr d, String params) { if (d.isPathIsUrl()) { return d.getPath(); } StringBuilder sb = new StringBuilder(80); if (stem != null) { sb.append(stem); if (stem.charAt(stem.length() - 1) != '/') { sb.append('/'); } } else { // ensure absolute path even if no scheme/host/port sb.append('/'); } sb.append(d.getPath()); if (params != null) { sb.append('?'); sb.append(params); } return sb.toString(); } String srvUrlStem(String host) { if (host == null) { return null; } StringBuilder sb = new StringBuilder(); sb.append(reqURL.getProtocol()); sb.append("://"); sb.append(host); sb.append(':'); sb.append(reqURL.getPort()); return sb.toString(); } /** Return a link to a servlet */ String srvLink(ServletDescr d, String text) { return srvLink(d, text, (String) null); } /** Return a link to a servlet with params */ String srvLink(ServletDescr d, String text, String params) { return new Link(srvURL(d, params), (text != null ? text : d.heading)).toString(); } /** Return a link to a servlet with params */ String srvLink(ServletDescr d, String text, Properties params) { return new Link(srvURL(d, params), text).toString(); } /** Return an absolute link to a servlet with params */ String srvAbsLink(ServletDescr d, String text, Properties params) { return srvAbsLink(d, text, concatParams(params)); } /** Return an absolute link to a servlet with params */ String srvAbsLink(ServletDescr d, String text, String params) { return new Link(srvAbsURL(d, params), (text != null ? text : d.heading)).toString(); } /** Return an absolute link to a servlet with params */ String srvAbsLink(String host, ServletDescr d, String text, String params) { return new Link(srvURL(host, d, params), (text != null ? text : d.heading)).toString(); } /** Return an absolute link to a servlet with params */ String srvAbsLink(PeerIdentity peer, ServletDescr d, String text, String params) { return new Link(srvURL(peer, d, params), (text != null ? text : d.heading)).toString(); } /** Return text as a link iff isLink */ String conditionalSrvLink(ServletDescr d, String text, String params, boolean isLink) { if (isLink) { return srvLink(d, text, params); } else { return text; } } /** Return text as a link iff isLink */ String conditionalSrvLink(ServletDescr d, String text, boolean isLink) { return conditionalSrvLink(d, text, null, isLink); } /** Concatenate params for URL string */ static String concatParams(String p1, String p2) { if (StringUtil.isNullString(p1)) { return p2; } if (StringUtil.isNullString(p2)) { return p1; } return p1 + "&" + p2; } /** Concatenate params for URL string */ String concatParams(Properties props) { if (props == null) { return null; } java.util.List list = new ArrayList(); for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) { String key = (String) iter.next(); String val = props.getProperty(key); if (!StringUtil.isNullString(val)) { list.add(key + "=" + urlEncode(val)); } } return StringUtil.separatedString(list, "&"); } String modifyParams(String key, String val) { Properties props = getParamsAsProps(); props.setProperty(key, val); return concatParams(props); } /** * Return the request parameters as a Properties. Only the first value of multivalued parameters * is included. */ Properties getParamsAsProps() { Properties props = new Properties(); for (Enumeration en = req.getParameterNames(); en.hasMoreElements(); ) { String name = (String) en.nextElement(); props.setProperty(name, req.getParameter(name)); } return props; } /** * Return the request parameters as a Map<String,String>. Only the first value of multivalued * parameters is included. */ Map<String, String> getParamsAsMap() { Map<String, String> map = new HashMap<String, String>(); for (Enumeration en = req.getParameterNames(); en.hasMoreElements(); ) { String name = (String) en.nextElement(); map.put(name, req.getParameter(name)); } return map; } protected String urlEncode(String param) { return UrlUtil.encodeUrl(param); } protected String getRequestKey() { String key = req.getPathInfo(); if (key != null && key.startsWith("/")) { return key.substring(1); } return key; } /** Common page setup. */ protected Page newPage() { // Compute heading String heading = getHeading(); if (heading == null) { heading = "Box Administration"; } // Create page and layout header Page page = ServletUtil.doNewPage(getPageTitle(), isFramed()); Iterator inNavIterator; if (myServletDescr().hasNoNavTable()) { inNavIterator = CollectionUtil.EMPTY_ITERATOR; } else { inNavIterator = new FilterIterator( new ObjectArrayIterator(getServletDescrs()), new Predicate() { public boolean evaluate(Object obj) { return isServletInNav((ServletDescr) obj); } }); } ServletUtil.layoutHeader( this, page, heading, isLargeLogo(), getMachineName(), getLockssApp().getStartDate(), inNavIterator); String warnMsg = CurrentConfig.getParam(PARAM_UI_WARNING); if (warnMsg != null) { Composite warning = new Composite(); warning.add("<center><font color=red size=+1>"); warning.add(warnMsg); warning.add("</font></center><br>"); page.add(warning); } return page; } protected Page addBarePageHeading(Page page) { // FIXME: Move the following fragment elsewhere // It causes the doctype statement to appear in the middle, // after the <body> tag. page.add("<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">"); page.addHeader("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">"); page.addHeader("<meta http-equiv=\"content-type\" content=\"text/html;charset=ISO-8859-1\">"); page.addHeader("<link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\" />"); return page; } private boolean isFramed = false; protected String errMsg; protected String statusMsg; protected boolean isFramed() { return isFramed; } protected void setFramed(boolean v) { isFramed = v; } protected String getPageTitle() { String heading = getHeading(); if (heading != null) { return "LOCKSS: " + heading; } else { return "LOCKSS"; } } /** Return a button that invokes the javascript submit routine with the specified action */ protected Element submitButton(String label, String action) { return submitButton(label, action, null, null); } /** Return a button that invokes javascript when clicked. */ Input jsButton(String label, String js) { Input btn = new Input("button", null); btn.attribute("value", label); setTabOrder(btn); btn.attribute("onClick", js); return btn; } /** * Return a button that invokes the javascript submit routine with the specified action, first * storing the value in the specified form prop. */ protected Element submitButton(String label, String action, String prop, String value) { StringBuilder sb = new StringBuilder(40); sb.append("lockssButton(this, '"); sb.append(action); sb.append("'"); if (prop != null && value != null) { sb.append(", '"); sb.append(prop); sb.append("', '"); sb.append(value); sb.append("'"); } sb.append(")"); Input btn = jsButton(label, sb.toString()); btn.attribute("id", "lsb." + (++submitButtonNumber)); return btn; } /** * Return a (possibly labelled) checkbox. * * @param label appears to right of checkbox if non null * @param value value included in result set if box checked * @param key form key to which result set is assigned * @param checked if true, box is initially checked * @return a checkbox Element */ Element checkBox(String label, String value, String key, boolean checked) { Input in = new Input(Input.Checkbox, key, value); if (checked) { in.check(); } setTabOrder(in); if (StringUtil.isNullString(label)) { return in; } else { Composite c = new Composite(); c.add(in); c.add(" "); c.add(label); return c; } } /** * Return a labelled rasio button * * @param label label to right of circle, and form value if checked * @param key form key to which value is assigned * @param checked if true, is initially checked * @return a readio button Element */ protected Element radioButton(String label, String key, boolean checked) { return radioButton(label, label, key, checked); } /** * Return a labelled rasio button * * @param label appears to right of circle if non null * @param value value assigned to key if box checked * @param key form key to which value is assigned * @param checked if true, is initially checked * @return a readio button Element */ protected Element radioButton(String label, String value, String key, boolean checked) { Composite c = new Composite(); Input in = new Input(Input.Radio, key, value); if (checked) { in.check(); } setTabOrder(in); c.add(in); c.add(label); return c; } /** Add html tags to grey the text if isGrey is true */ protected String greyText(String txt, boolean isGrey) { if (!isGrey) { return txt; } return "<font color=gray>" + txt + "</font>"; } /** * Set this element next in the tab order. Returns the element for easier nesting in expressions. */ protected Element setTabOrder(Element ele) { ele.attribute("tabindex", tabindex++); return ele; } /** * Store a footnote, assign it a number, return html for footnote reference. If footnote in null * or empty, no footnote is added and an empty string is returned. Footnote numbers get turned * into links; <b>Do not put the result of addFootnote inside a link!</b>. */ protected String addFootnote(String s) { if (s == null || s.length() == 0) { return ""; } if (footNumber == 0) { if (footnotes == null) { footnotes = new Vector(10, 10); } else { footnotes.removeAllElements(); } } int n = footnotes.indexOf(s); if (n < 0) { n = footNumber++; footnotes.addElement(s); } return "<sup><font size=-1><a href=#foottag" + (n + 1) + ">" + (n + 1) + "</a></font></sup>"; } /** * Add javascript to page. Normally adds a link to the script file, but can be told to include the * script directly in the page, to accomodate unit testing of individual servlets, when other * fetches won't work. */ protected void addJavaScript(Composite comp) { String include = (String) context.getAttribute(ATTR_INCLUDE_SCRIPT); if (StringUtil.isNullString(include)) { linkToJavaScript(comp); } else { includeJavaScript0(comp); } } private void includeJavaScript0(Composite comp) { Script script = new Script(getJavascript()); comp.add(script); } private void linkToJavaScript(Composite comp) { Script script = new Script(""); script.attribute("src", "admin.js"); comp.add(script); } private static String jstext = null; private static synchronized String getJavascript() { if (jstext == null) { InputStream istr = null; try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); istr = loader.getResourceAsStream(JAVASCRIPT_RESOURCE); jstext = StringUtil.fromInputStream(istr); istr.close(); } catch (Exception e) { log.error("Can't load javascript", e); } finally { IOUtil.safeClose(istr); } } return jstext; } /** Display a message in lieu of the normal page */ protected void displayMsgInLieuOfPage(String msg) throws IOException { // TODO: Look at HTML Page page = newPage(); Composite warning = new Composite(); warning.add(msg); warning.add("<br>"); page.add(warning); layoutFooter(page); page.write(resp.getWriter()); } /** Display a warning in red, in lieu of the normal page */ protected void displayWarningInLieuOfPage(String msg) throws IOException { displayMsgInLieuOfPage("<center><font color=red size=+1>" + msg + "</font></center>"); } /** Display "The cache isn't ready yet, come back later" */ protected void displayNotStarted() throws IOException { displayWarningInLieuOfPage( "This LOCKSS box is still starting. Please " + srvLink(myServletDescr(), "try again", getParamsAsProps()) + " in a moment."); } public MultiPartRequest getMultiPartRequest() throws FormDataTooLongException, IOException { int maxUpload = CurrentConfig.getIntParam(PARAM_MAX_UPLOAD_FILE_SIZE, DEFAULT_MAX_UPLOAD_FILE_SIZE); return getMultiPartRequest(maxUpload); } public MultiPartRequest getMultiPartRequest(int maxLen) throws FormDataTooLongException, IOException { if (req.getContentType() == null || !req.getContentType().startsWith("multipart/form-data")) { return null; } if (req.getContentLength() > maxLen) { throw new FormDataTooLongException(req.getContentLength() + " bytes, " + maxLen + " allowed"); } MultiPartRequest multi = new MultiPartRequest(req); if (log.isDebug2()) { String[] parts = multi.getPartNames(); log.debug3("Multipart request, " + parts.length + " parts"); if (log.isDebug3()) { for (int p = 0; p < parts.length; p++) { String name = parts[p]; String cont = multi.getString(parts[p]); log.debug3(name + ": " + cont); } } } multiReq = multi; return multi; } public String getParameter(String name) { String val = req.getParameter(name); if (val == null && multiReq != null) { val = multiReq.getString(name); } if (val == null) { return null; } val = StringUtils.strip(val, " \t"); // if (StringUtil.isNullString(val)) { if ("".equals(val)) { return null; } return val; } protected void layoutFooter(Page page) { ServletUtil.doLayoutFooter( page, (footnotes == null ? null : footnotes.iterator()), getLockssApp().getVersionInfo()); if (footnotes != null) { footnotes.removeAllElements(); } } /** Return the app instance. */ protected LockssApp getLockssApp() { return theApp; } /** * Return the daemon instance, assumes that the servlet is running in the daemon. * * @throws ClassCastException if the servlet is running in an app other than the daemon */ protected LockssDaemon getLockssDaemon() { return (LockssDaemon) theApp; } protected void logParams() { Enumeration en = req.getParameterNames(); while (en.hasMoreElements()) { String name = (String) en.nextElement(); String vals[]; String dispval; if (StringUtil.indexOfIgnoreCase(name, "passw") >= 0) { dispval = req.getParameter(name).length() == 0 ? "" : "********"; } else if (log.isDebug2() && (vals = req.getParameterValues(name)).length > 1) { dispval = StringUtil.separatedString(vals, ", "); } else { dispval = req.getParameter(name); } log.debug(name + " = " + dispval); } } /** Convenience method */ protected String encodeText(String s) { return HtmlUtil.encode(s, HtmlUtil.ENCODE_TEXT); } /** Convenience method */ protected String encodeTextArea(String s) { return HtmlUtil.encode(s, HtmlUtil.ENCODE_TEXTAREA); } /** Convenience method */ protected String encodeAttr(String s) { return HtmlUtil.encode(s, HtmlUtil.ENCODE_ATTR); } /** * Create message and error message block * * @param composite TODO */ protected void layoutErrorBlock(Composite composite) { if (errMsg != null || statusMsg != null) { ServletUtil.layoutErrorBlock(composite, errMsg, statusMsg); } } /** Exception thrown if multipart form data is longer than the caller-supplied max */ public static class FormDataTooLongException extends Exception { public FormDataTooLongException(String message) { super(message); } } }
/** UI to invoke various daemon actions */ @SuppressWarnings("serial") public class DebugPanel extends LockssServlet { public static final String PREFIX = Configuration.PREFIX + "debugPanel."; /** Priority for crawls started from the debug panel */ public static final String PARAM_CRAWL_PRIORITY = PREFIX + "crawlPriority"; public static final int DEFAULT_CRAWL_PRIORITY = 10; /** Priority for crawls started from the debug panel */ public static final String PARAM_ENABLE_DEEP_CRAWL = PREFIX + "deepCrawlEnabled"; private static final boolean DEFAULT_ENABLE_DEEP_CRAWL = false; static final String KEY_ACTION = "action"; static final String KEY_MSG = "msg"; static final String KEY_NAME_SEL = "name_sel"; static final String KEY_NAME_TYPE = "name_type"; static final String KEY_AUID = "auid"; static final String KEY_URL = "url"; static final String KEY_REFETCH_DEPTH = "depth"; static final String KEY_TIME = "time"; static final String ACTION_MAIL_BACKUP = "Mail Backup File"; static final String ACTION_THROW_IOEXCEPTION = "Throw IOException"; static final String ACTION_FIND_URL = "Find Preserved URL"; public static final String ACTION_REINDEX_METADATA = "Reindex Metadata"; public static final String ACTION_FORCE_REINDEX_METADATA = "Force Reindex Metadata"; public static final String ACTION_START_V3_POLL = "Start V3 Poll"; static final String ACTION_FORCE_START_V3_POLL = "Force V3 Poll"; public static final String ACTION_START_CRAWL = "Start Crawl"; public static final String ACTION_FORCE_START_CRAWL = "Force Start Crawl"; public static final String ACTION_START_DEEP_CRAWL = "Deep Crawl"; public static final String ACTION_FORCE_START_DEEP_CRAWL = "Force Deep Crawl"; public static final String ACTION_CHECK_SUBSTANCE = "Check Substance"; static final String ACTION_CRAWL_PLUGINS = "Crawl Plugins"; static final String ACTION_RELOAD_CONFIG = "Reload Config"; static final String ACTION_SLEEP = "Sleep"; public static final String ACTION_DISABLE_METADATA_INDEXING = "Disable Indexing"; /** Set of actions for which audit alerts shouldn't be generated */ public static final Set noAuditActions = SetUtil.set(ACTION_FIND_URL); static final String COL2 = "colspan=2"; static final String COL2CENTER = COL2 + " align=center"; static Logger log = Logger.getLogger("DebugPanel"); private LockssDaemon daemon; private PluginManager pluginMgr; private PollManager pollManager; private CrawlManager crawlMgr; private ConfigManager cfgMgr; private DbManager dbMgr; private MetadataManager metadataMgr; private RemoteApi rmtApi; boolean showResult; boolean showForcePoll; boolean showForceCrawl; boolean showForceReindexMetadata; String formAuid; String formDepth = "100"; protected void resetLocals() { resetVars(); super.resetLocals(); } void resetVars() { formAuid = null; errMsg = null; statusMsg = null; showForcePoll = false; showForceCrawl = false; showForceReindexMetadata = false; } public void init(ServletConfig config) throws ServletException { super.init(config); daemon = getLockssDaemon(); pluginMgr = daemon.getPluginManager(); pollManager = daemon.getPollManager(); crawlMgr = daemon.getCrawlManager(); cfgMgr = daemon.getConfigManager(); rmtApi = daemon.getRemoteApi(); try { dbMgr = daemon.getDbManager(); metadataMgr = daemon.getMetadataManager(); } catch (IllegalArgumentException ex) { } } public void lockssHandleRequest() throws IOException { resetVars(); boolean showForm = true; String action = getParameter(KEY_ACTION); if (!StringUtil.isNullString(action)) { formAuid = getParameter(KEY_AUID); formDepth = getParameter(KEY_REFETCH_DEPTH); UserAccount acct = getUserAccount(); if (acct != null && !noAuditActions.contains(action)) { acct.auditableEvent("used debug panel action: " + action + " AU ID: " + formAuid); } } if (ACTION_MAIL_BACKUP.equals(action)) { doMailBackup(); } if (ACTION_RELOAD_CONFIG.equals(action)) { doReloadConfig(); } if (ACTION_SLEEP.equals(action)) { doSleep(); } if (ACTION_THROW_IOEXCEPTION.equals(action)) { doThrow(); } if (ACTION_START_V3_POLL.equals(action)) { doV3Poll(); } if (ACTION_FORCE_START_V3_POLL.equals(action)) { forceV3Poll(); } if (ACTION_START_CRAWL.equals(action)) { doCrawl(false, false); } if (ACTION_FORCE_START_CRAWL.equals(action)) { doCrawl(true, false); } if (ACTION_START_DEEP_CRAWL.equals(action)) { doCrawl(false, true); } if (ACTION_FORCE_START_DEEP_CRAWL.equals(action)) { doCrawl(true, true); } if (ACTION_CHECK_SUBSTANCE.equals(action)) { doCheckSubstance(); } if (ACTION_CRAWL_PLUGINS.equals(action)) { crawlPluginRegistries(); } if (ACTION_FIND_URL.equals(action)) { showForm = doFindUrl(); } if (ACTION_REINDEX_METADATA.equals(action)) { doReindexMetadata(); } if (ACTION_FORCE_REINDEX_METADATA.equals(action)) { forceReindexMetadata(); } if (ACTION_DISABLE_METADATA_INDEXING.equals(action)) { doDisableMetadataIndexing(); } if (showForm) { displayPage(); } } private void doMailBackup() { try { rmtApi.createConfigBackupFile(RemoteApi.BackupFileDisposition.Mail); } catch (Exception e) { errMsg = "Error: " + e.getMessage(); } } private void doReloadConfig() { cfgMgr.requestReload(); } private void doThrow() throws IOException { String msg = getParameter(KEY_MSG); throw new IOException(msg != null ? msg : "Test message"); } private void doSleep() throws IOException { String timestr = getParameter(KEY_TIME); try { long time = StringUtil.parseTimeInterval(timestr); Deadline.in(time).sleep(); statusMsg = "Slept for " + StringUtil.timeIntervalToString(time); } catch (NumberFormatException e) { errMsg = "Illegal duration: " + e; } catch (InterruptedException e) { errMsg = "Interrupted: " + e; } } private void doReindexMetadata() { ArchivalUnit au = getAu(); if (au == null) return; try { startReindexingMetadata(au, false); } catch (RuntimeException e) { log.error("Can't reindex metadata", e); errMsg = "Error: " + e.toString(); } } private void forceReindexMetadata() { ArchivalUnit au = getAu(); if (au == null) return; try { startReindexingMetadata(au, true); } catch (RuntimeException e) { log.error("Can't reindex metadata", e); errMsg = "Error: " + e.toString(); } } private void doDisableMetadataIndexing() { ArchivalUnit au = getAu(); if (au == null) return; try { disableMetadataIndexing(au, false); } catch (RuntimeException e) { log.error("Can't disable metadata indexing", e); errMsg = "Error: " + e.toString(); } } private void doCrawl(boolean force, boolean deep) { ArchivalUnit au = getAu(); if (au == null) return; try { startCrawl(au, force, deep); } catch (CrawlManagerImpl.NotEligibleException.RateLimiter e) { errMsg = "AU has crawled recently (" + e.getMessage() + "). Click again to override."; showForceCrawl = true; return; } catch (CrawlManagerImpl.NotEligibleException e) { errMsg = "Can't enqueue crawl: " + e.getMessage(); } } private void crawlPluginRegistries() { StringBuilder sb = new StringBuilder(); for (ArchivalUnit au : pluginMgr.getAllRegistryAus()) { sb.append(au.getName()); sb.append(": "); try { startCrawl(au, true, false); sb.append("Queued."); } catch (CrawlManagerImpl.NotEligibleException e) { sb.append("Failed: "); sb.append(e.getMessage()); } sb.append("\n"); } statusMsg = sb.toString(); } private boolean startCrawl(ArchivalUnit au, boolean force, boolean deep) throws CrawlManagerImpl.NotEligibleException { CrawlManagerImpl cmi = (CrawlManagerImpl) crawlMgr; if (force) { RateLimiter limit = cmi.getNewContentRateLimiter(au); if (!limit.isEventOk()) { limit.unevent(); } } cmi.checkEligibleToQueueNewContentCrawl(au); String delayMsg = ""; String deepMsg = ""; try { cmi.checkEligibleForNewContentCrawl(au); } catch (CrawlManagerImpl.NotEligibleException e) { delayMsg = ", Start delayed due to: " + e.getMessage(); } Configuration config = ConfigManager.getCurrentConfig(); int pri = config.getInt(PARAM_CRAWL_PRIORITY, DEFAULT_CRAWL_PRIORITY); CrawlReq req; try { req = new CrawlReq(au); req.setPriority(pri); if (deep) { int d = Integer.parseInt(formDepth); if (d < 0) { errMsg = "Illegal refetch depth: " + d; return false; } req.setRefetchDepth(d); deepMsg = "Deep (" + req.getRefetchDepth() + ") "; } } catch (NumberFormatException e) { errMsg = "Illegal refetch depth: " + formDepth; return false; } catch (RuntimeException e) { log.error("Couldn't create CrawlReq: " + au, e); errMsg = "Couldn't create CrawlReq: " + e.toString(); return false; } cmi.startNewContentCrawl(req, null); statusMsg = deepMsg + "Crawl requested for " + au.getName() + delayMsg; return true; } private void doCheckSubstance() { ArchivalUnit au = getAu(); if (au == null) return; try { checkSubstance(au); } catch (RuntimeException e) { log.error("Error in SubstanceChecker", e); errMsg = "Error in SubstanceChecker; see log."; } } private void checkSubstance(ArchivalUnit au) { SubstanceChecker subChecker = new SubstanceChecker(au); if (!subChecker.isEnabled()) { errMsg = "No substance patterns defined for plugin."; return; } AuState auState = AuUtil.getAuState(au); SubstanceChecker.State oldState = auState.getSubstanceState(); SubstanceChecker.State newState = subChecker.findSubstance(); String chtxt = (newState == oldState ? "(unchanged)" : "(was " + oldState.toString() + ")"); switch (newState) { case Unknown: log.error("Shouldn't happen: SubstanceChecker returned Unknown"); errMsg = "Error in SubstanceChecker; see log."; break; case Yes: statusMsg = "AU has substance " + chtxt + ": " + au.getName(); auState.setSubstanceState(SubstanceChecker.State.Yes); break; case No: statusMsg = "AU has no substance " + chtxt + ": " + au.getName(); auState.setSubstanceState(SubstanceChecker.State.No); break; } } private boolean startReindexingMetadata(ArchivalUnit au, boolean force) { if (metadataMgr == null) { errMsg = "Metadata processing is not enabled."; return false; } if (!force) { if (!AuUtil.hasCrawled(au)) { errMsg = "Au has never crawled. Click again to reindex metadata"; showForceReindexMetadata = true; return false; } AuState auState = AuUtil.getAuState(au); switch (auState.getSubstanceState()) { case No: errMsg = "Au has no substance. Click again to reindex metadata"; showForceReindexMetadata = true; return false; case Unknown: errMsg = "Unknown substance for Au. Click again to reindex metadata."; showForceReindexMetadata = true; return false; case Yes: // fall through } } // Fully reindex metadata with the highest priority. Connection conn = null; PreparedStatement insertPendingAuBatchStatement = null; try { conn = dbMgr.getConnection(); insertPendingAuBatchStatement = metadataMgr.getPrioritizedInsertPendingAuBatchStatement(conn); if (metadataMgr.enableAndAddAuToReindex( au, conn, insertPendingAuBatchStatement, false, true)) { statusMsg = "Reindexing metadata for " + au.getName(); return true; } } catch (DbException dbe) { log.error("Cannot reindex metadata for " + au.getName(), dbe); } finally { DbManager.safeCloseStatement(insertPendingAuBatchStatement); DbManager.safeRollbackAndClose(conn); } if (force) { errMsg = "Still cannot reindex metadata for " + au.getName(); } else { errMsg = "Cannot reindex metadata for " + au.getName(); } return false; } private boolean disableMetadataIndexing(ArchivalUnit au, boolean force) { if (metadataMgr == null) { errMsg = "Metadata processing is not enabled."; return false; } try { metadataMgr.disableAuIndexing(au); statusMsg = "Disabled metadata indexing for " + au.getName(); return true; } catch (Exception e) { errMsg = "Cannot reindex metadata for " + au.getName() + ": " + e.getMessage(); return false; } } private void doV3Poll() { ArchivalUnit au = getAu(); if (au == null) return; try { callV3ContentPoll(au); } catch (PollManager.NotEligibleException e) { errMsg = "AU is not eligible for poll: " + e.getMessage(); // errMsg = "Ineligible: " + e.getMessage() + // "<br>Click again to force new poll."; // showForcePoll = true; return; } catch (Exception e) { log.error("Can't start poll", e); errMsg = "Error: " + e.toString(); } } private void forceV3Poll() { ArchivalUnit au = getAu(); if (au == null) return; try { callV3ContentPoll(au); } catch (Exception e) { log.error("Can't start poll", e); errMsg = "Error: " + e.toString(); } } private void callV3ContentPoll(ArchivalUnit au) throws PollManager.NotEligibleException { log.debug("Enqueuing a V3 Content Poll on " + au.getName()); PollSpec spec = new PollSpec(au.getAuCachedUrlSet(), Poll.V3_POLL); pollManager.enqueueHighPriorityPoll(au, spec); statusMsg = "Enqueued V3 poll for " + au.getName(); } private boolean doFindUrl() throws IOException { String url = getParameter(KEY_URL); String redir = srvURL( AdminServletManager.SERVLET_DAEMON_STATUS, PropUtil.fromArgs("table", ArchivalUnitStatus.AUS_WITH_URL_TABLE_NAME, "key", url)); resp.setContentLength(0); // resp.sendRedirect(resp.encodeRedirectURL(redir)); resp.sendRedirect(redir); return false; } ArchivalUnit getAu() { if (StringUtil.isNullString(formAuid)) { errMsg = "Select an AU"; return null; } ArchivalUnit au = pluginMgr.getAuFromId(formAuid); if (au == null) { errMsg = "No such AU. Select an AU"; return null; } return au; } private void displayPage() throws IOException { Page page = newPage(); layoutErrorBlock(page); ServletUtil.layoutExplanationBlock(page, "Debug Actions"); page.add(makeForm()); page.add("<br>"); endPage(page); } private Element makeForm() { Composite comp = new Composite(); Form frm = new Form(srvURL(myServletDescr())); frm.method("POST"); frm.add("<br><center>"); Input reload = new Input(Input.Submit, KEY_ACTION, ACTION_RELOAD_CONFIG); setTabOrder(reload); frm.add(reload); frm.add(" "); Input backup = new Input(Input.Submit, KEY_ACTION, ACTION_MAIL_BACKUP); setTabOrder(backup); frm.add(backup); frm.add(" "); Input crawlplug = new Input(Input.Submit, KEY_ACTION, ACTION_CRAWL_PLUGINS); setTabOrder(crawlplug); frm.add(crawlplug); frm.add("</center>"); ServletDescr d1 = AdminServletManager.SERVLET_HASH_CUS; if (isServletRunnable(d1)) { frm.add("<br><center>" + srvLink(d1, d1.heading) + "</center>"); } Input findUrl = new Input(Input.Submit, KEY_ACTION, ACTION_FIND_URL); Input findUrlText = new Input(Input.Text, KEY_URL); findUrlText.setSize(50); setTabOrder(findUrl); setTabOrder(findUrlText); frm.add("<br><center>" + findUrl + " " + findUrlText + "</center>"); Input thrw = new Input(Input.Submit, KEY_ACTION, ACTION_THROW_IOEXCEPTION); Input thmsg = new Input(Input.Text, KEY_MSG); setTabOrder(thrw); setTabOrder(thmsg); frm.add("<br><center>" + thrw + " " + thmsg + "</center>"); frm.add("<br><center>AU Actions: select AU</center>"); Composite ausel = ServletUtil.layoutSelectAu(this, KEY_AUID, formAuid); frm.add("<br><center>" + ausel + "</center>"); setTabOrder(ausel); Input v3Poll = new Input( Input.Submit, KEY_ACTION, (showForcePoll ? ACTION_FORCE_START_V3_POLL : ACTION_START_V3_POLL)); Input crawl = new Input( Input.Submit, KEY_ACTION, (showForceCrawl ? ACTION_FORCE_START_CRAWL : ACTION_START_CRAWL)); frm.add("<br><center>"); frm.add(v3Poll); frm.add(" "); frm.add(crawl); if (CurrentConfig.getBooleanParam(PARAM_ENABLE_DEEP_CRAWL, DEFAULT_ENABLE_DEEP_CRAWL)) { Input deepCrawl = new Input( Input.Submit, KEY_ACTION, (showForceCrawl ? ACTION_FORCE_START_DEEP_CRAWL : ACTION_START_DEEP_CRAWL)); Input depthText = new Input(Input.Text, KEY_REFETCH_DEPTH, formDepth); depthText.setSize(4); setTabOrder(depthText); frm.add(" "); frm.add(deepCrawl); frm.add(depthText); } Input checkSubstance = new Input(Input.Submit, KEY_ACTION, ACTION_CHECK_SUBSTANCE); frm.add("<br>"); frm.add(checkSubstance); if (metadataMgr != null) { Input reindex = new Input( Input.Submit, KEY_ACTION, (showForceReindexMetadata ? ACTION_FORCE_REINDEX_METADATA : ACTION_REINDEX_METADATA)); frm.add(" "); frm.add(reindex); Input disableIndexing = new Input(Input.Submit, KEY_ACTION, ACTION_DISABLE_METADATA_INDEXING); frm.add(" "); frm.add(disableIndexing); } frm.add("</center>"); comp.add(frm); return comp; } }