org.lockss.servlet.DisplayContentStatus.java Source code

Java tutorial

Introduction

Here is the source code for org.lockss.servlet.DisplayContentStatus.java

Source

/*
 * $Id DisplayContentStatus.java 2012/12/03 14:52:00 rwincewicz $
 */

/*
    
 Copyright (c) 2000-2014 Board of Trustees of Leland Stanford Jr. University,
 all rights reserved.
    
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:
    
 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.
    
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 STANFORD UNIVERSITY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    
 Except as contained in this notice, the name of Stanford University shall not
 be used in advertising or otherwise to promote the sale, use or other dealings
 in this Software without prior written authorization from Stanford University.
    
 */
package org.lockss.servlet;

import java.io.*;
import java.net.URLDecoder;
import java.text.*;
import java.util.*;
import javax.servlet.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.lang3.time.FastDateFormat;
import org.lockss.config.*;
import org.lockss.daemon.status.*;
import org.lockss.plugin.ArchivalUnit;
import org.lockss.plugin.PluginManager;
import org.lockss.remote.RemoteApi;
import org.lockss.util.*;
import org.mortbay.html.*;
import org.w3c.dom.Document;

/**
 * Display Content Status servlet
 */
public class DisplayContentStatus extends LockssServlet {

    private static final Logger log = Logger.getLogger(DisplayContentStatus.class);

    public static final String AU_TO_REMOVE = "removeAu";
    /**
     * Supported output formats
     */
    static final int OUTPUT_HTML = 1;
    static final int OUTPUT_TEXT = 2;
    static final int OUTPUT_XML = 3;
    static final int OUTPUT_CSV = 4;
    private String tableName;
    private String tableKey;
    private String sortKey;
    private String groupKey;
    private String typeKey;
    private String filterKey;
    private String tabKey;
    private String timeKey;
    private StatusService statSvc;
    private int outputFmt;
    private java.util.List rules;
    private BitSet tableOptions;
    private PluginManager pluginMgr;
    private RemoteApi remoteApi;
    private String action;
    private String auName;
    private String deleteMessage;

    protected void resetLocals() {
        super.resetLocals();
        rules = null;
    }

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        statSvc = getLockssDaemon().getStatusService();
        pluginMgr = getLockssDaemon().getPluginManager();
        remoteApi = getLockssDaemon().getRemoteApi();
    }

    static final Set fixedParams = SetUtil.set("text", "output", "options", "table", "key", "sort");

    /**
     * Handle a request
     *
     * @throws IOException
     */
    public void lockssHandleRequest() throws IOException {
        if (!StringUtil.isNullString(req.getParameter("isDaemonReady"))) {
            if (pluginMgr.areAusStarted()) {
                resp.setStatus(200);
                PrintWriter wrtr = resp.getWriter();
                resp.setContentType("text/plain");
                wrtr.println("true");
            } else {
                PrintWriter wrtr = resp.getWriter();
                resp.setContentType("text/plain");
                wrtr.println("false");
                resp.sendError(202, "Not ready");
            }
            return;
        }

        action = req.getParameter(ACTION_TAG);
        auName = req.getParameter(AU_TO_REMOVE);
        if ("Delete selected".equals(req.getParameter("submit"))) {
            String[] deleteAUs = req.getParameterValues("deleteAu");
            if (deleteAUs != null) {
                log.error("AUs: " + Arrays.asList(deleteAUs));
                doRemoveAus(Arrays.asList(deleteAUs));
            } else {
                log.error("No AUs selected");
                deleteMessage = "No AUs selected!";
            }
        }

        String publisher = req.getParameter("deletePublisher");
        if (!StringUtil.isNullString(publisher)) {
            TreeMap<String, TreeMap<String, TreeSet<ArchivalUnit>>> auMap = DisplayContentTab
                    .getAusByPublisherName();
            ArrayList<String> auIds = new ArrayList<String>();
            if (auMap.containsKey(publisher)) {
                Iterator it = auMap.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry pairs = (Map.Entry) it.next();
                    String publisherString = pairs.getKey().toString();
                    log.error("Publisher: " + publisher);
                    log.error("Publisher string: " + publisherString);
                    if (publisher.equals(publisherString)) {
                        TreeMap<String, TreeSet<ArchivalUnit>> titleMap = (TreeMap<String, TreeSet<ArchivalUnit>>) pairs
                                .getValue();
                        Iterator titleIterator = titleMap.entrySet().iterator();
                        while (titleIterator.hasNext()) {
                            Map.Entry titlePairs = (Map.Entry) titleIterator.next();
                            TreeSet<ArchivalUnit> auSet = (TreeSet<ArchivalUnit>) titlePairs.getValue();
                            for (ArchivalUnit au : auSet) {
                                auIds.add(au.getAuId());
                            }
                        }
                    }
                }
                doRemoveAus(auIds);
            }
        }

        if (action != null && auName != null) {
            String auString = URLDecoder.decode(auName, "UTF-8");
            java.util.List<String> auList = new ArrayList<String>();
            auList.add(auString);
            doRemoveAus(auList);
        }

        if (StringUtil.isNullString(action)) {
            try {
                getMultiPartRequest();
                if (multiReq != null) {
                    action = multiReq.getString(ACTION_TAG);
                }
            } catch (FormDataTooLongException e) {
                errMsg = "Uploaded file too large: " + e.getMessage();
                // leave action null, will call displayAuSummary() below
            }
        }

        outputFmt = OUTPUT_HTML; // default output is html

        String outputParam = req.getParameter("output");
        if (!StringUtil.isNullString(outputParam)) {
            if ("html".equalsIgnoreCase(outputParam)) {
                outputFmt = OUTPUT_HTML;
            } else if ("xml".equalsIgnoreCase(outputParam)) {
                outputFmt = OUTPUT_XML;
            } else if ("text".equalsIgnoreCase(outputParam)) {
                outputFmt = OUTPUT_TEXT;
            } else if ("csv".equalsIgnoreCase(outputParam)) {
                outputFmt = OUTPUT_CSV;
            } else {
                log.warning("Unknown output format: " + outputParam);
            }
        }
        String optionsParam = req.getParameter("options");

        tableOptions = new BitSet();

        if (isDebugUser()) {
            log.debug2("Debug user.  Setting OPTION_DEBUG_USER");
            tableOptions.set(StatusTable.OPTION_DEBUG_USER);
        }

        for (Iterator iter = StringUtil.breakAt(optionsParam, ',').iterator(); iter.hasNext();) {
            String s = (String) iter.next();
            if ("norows".equalsIgnoreCase(s)) {
                tableOptions.set(StatusTable.OPTION_NO_ROWS);
            }
        }

        tableName = req.getParameter("table");
        tableKey = req.getParameter("key");
        if (StringUtil.isNullString(tableName)) {
            tableName = "AuOverview";
        }
        if (StringUtil.isNullString(tableKey)) {
            tableKey = null;
        }
        sortKey = req.getParameter("sort");
        if (StringUtil.isNullString(sortKey)) {
            sortKey = null;
        }
        groupKey = req.getParameter("group");
        if (StringUtil.isNullString(groupKey)) {
            groupKey = "publisher";
        }
        typeKey = req.getParameter("type");
        filterKey = req.getParameter("filterKey");
        tabKey = req.getParameter("tab");
        timeKey = req.getParameter("timeKey");

        switch (outputFmt) {
        case OUTPUT_HTML:
            doHtmlStatusTable();
            break;
        case OUTPUT_XML:
            try {
                doXmlStatusTable();
            } catch (Exception e) {
                throw new IOException("Error building XML", e);
            }
            break;
        case OUTPUT_TEXT:
            doTextStatusTable();
            break;
        case OUTPUT_CSV:
            doCsvStatusTable();
            break;
        }
    }

    private Page newTablePage() throws IOException {
        Page page = newPage();
        addJavaScript(page);

        if (!pluginMgr.areAusStarted()) {
            page.add(ServletUtil.notStartedWarning());
        }

        // After resp.getWriter() has been called, throwing an exception will
        // result in a blank page, so don't call it until the end.
        // (HttpResponse.sendError() calls getOutputStream(), and only one of
        // getWriter() or getOutputStream() may be called.)

        // all pages but index get a select box to choose a different table
        if (!isAllTablesTable()) {
            page.add(ServletUtil.removeElementWithId("dsSelectBox"));
        }

        //       page.add("<center>");
        //       page.add(srvLink(SERVLET_DAEMON_STATUS, ".",
        //              concatParams("text=1", req.getQueryString())));
        //       page.add("</center><br><br>");
        //    page.add(deleteMessage);
        deleteMessage = null;
        DisplayContentTable content = new DisplayContentTable(page, groupKey, typeKey, filterKey, timeKey);
        //    Script selectTab = new Script("$( \"#tabs\" ).tabs(\"select\", 3);");
        //    page.addHeader(selectTab);
        return page;
    }

    private void doRemoveAus(java.util.List aus) throws IOException {
        remoteApi.deleteAus(aus);
    }

    private void doHtmlStatusTable() throws IOException {
        Page page = doHtmlStatusTable0();
        endPage(page);
    }

    private void doTextStatusTable() throws IOException {
        PrintWriter wrtr = resp.getWriter();
        resp.setContentType("text/plain");

        //     String vPlatform = CurrentConfig.getParam(PARAM_PLATFORM_VERSION);
        String vPlatform;
        PlatformVersion pVer = ConfigManager.getPlatformVersion();
        if (pVer != null) {
            vPlatform = ", platform=" + StringUtil.ckvEscape(pVer.displayString());
        } else {
            vPlatform = "";
        }
        Date now = new Date();
        Date startDate = getLockssDaemon().getStartDate();
        wrtr.println("host=" + getLcapIPAddr() + ",time=" + now.getTime() + ",up="
                + TimeBase.msSince(startDate.getTime()) + ",version=" + BuildInfo.getBuildInfoString() + vPlatform);
        doTextStatusTable(wrtr);
    }

    private StatusTable makeTable() throws StatusService.NoSuchTableException {
        StatusTable table = new StatusTable(tableName, tableKey);
        table.setOptions(tableOptions);
        for (Enumeration en = req.getParameterNames(); en.hasMoreElements();) {
            String name = (String) en.nextElement();
            if (!fixedParams.contains(name)) {
                table.setProperty(name, req.getParameter(name));
            }
        }
        statSvc.fillInTable(table);
        return table;
    }

    // build and send an XML DOM Document of the StatusTable
    private void doXmlStatusTable() throws Exception {
        // By default, XmlDomBuilder will produce UTF-8.  Must set content type
        // *before* calling getWriter()
        resp.setContentType("text/xml; charset=UTF-8");
        PrintWriter wrtr = resp.getWriter();

        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

        Document doc = docBuilder.newDocument();
        org.w3c.dom.Element rootElement = doc.createElement("root");
        doc.appendChild(rootElement);

        org.w3c.dom.Element notImplemented = doc.createElement("notImplemented");
        rootElement.appendChild(notImplemented);

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        javax.xml.transform.Transformer transformer = transformerFactory.newTransformer();
        DOMSource domSource = new DOMSource(doc);
        StringWriter stringWriter = new StringWriter();
        StreamResult streamResult = new StreamResult(stringWriter);
        transformer.transform(domSource, streamResult);

        wrtr.append(stringWriter.toString());
    }

    private java.util.List getRowList(StatusTable statTable) {
        java.util.List rowList;
        if (sortKey != null) {
            try {
                rules = makeSortRules(statTable, sortKey);
                rowList = statTable.getSortedRows(rules);
            } catch (Exception e) {
                // There are lots of ways a user-specified sort can fail if the
                // table creator isn't careful.  Fall back to default if that
                // happens.
                log.warning("Error sorting table by: " + rules, e);
                // XXX should display some sort of error msg
                rowList = statTable.getSortedRows();
                rules = null; // prevent column titles from indicating
                // the sort order that didn't work
            }
        } else {
            rowList = statTable.getSortedRows();
        }
        return rowList;
    }

    // Build the table, adding elements to page
    private Page doHtmlStatusTable0() throws IOException {
        Page page;
        page = newTablePage();
        if (tabKey != null) {
            Script tabSelect = new Script(
                    "$(document).ready(function () {$(\"#tabs\").tabs({ active: " + tabKey + " })});");
            page.addHeader(tabSelect);
        }
        return page;
    }

    // Build the table, writing text to wrtr
    private void doTextStatusTable(PrintWriter wrtr) throws IOException {
        StatusTable statTable;
        try {
            statTable = makeTable();
        } catch (StatusService.NoSuchTableException e) {
            wrtr.println("No table: " + e.toString());
            return;
        } catch (Exception e) {
            wrtr.println("Error getting table: " + e.toString());
            return;
        }
        wrtr.println();
        wrtr.print("table=" + StringUtil.ckvEscape(statTable.getTitle()));
        if (tableKey != null) {
            wrtr.print(",key=" + StringUtil.ckvEscape(tableKey));
        }

        java.util.List summary = statTable.getSummaryInfo();
        if (summary != null && !summary.isEmpty()) {
            for (Iterator iter = summary.iterator(); iter.hasNext();) {
                StatusTable.SummaryInfo sInfo = (StatusTable.SummaryInfo) iter.next();
                wrtr.print(",");
                wrtr.print(sInfo.getTitle());
                wrtr.print("=");
                Object dispVal = getTextDisplayString(sInfo.getValue());
                String valStr = dispVal != null ? dispVal.toString() : "(null)";
                wrtr.print(StringUtil.ckvEscape(valStr));
            }
        }
        wrtr.println();

        java.util.List rowList = getRowList(statTable);
        if (rowList != null) {
            // output rows
            for (Iterator rowIter = rowList.iterator(); rowIter.hasNext();) {
                Map rowMap = (Map) rowIter.next();
                for (Iterator iter = rowMap.keySet().iterator(); iter.hasNext();) {
                    Object o = iter.next();
                    if (!(o instanceof String)) {
                        // ignore special markers (eg, StatusTable.ROW_SEPARATOR)
                        continue;
                    }
                    String key = (String) o;
                    Object val = rowMap.get(key);
                    Object dispVal = getTextDisplayString(val);
                    String valStr = dispVal != null ? dispVal.toString() : "(null)";
                    wrtr.print(key + "=" + StringUtil.ckvEscape(valStr));
                    if (iter.hasNext()) {
                        wrtr.print(",");
                    } else {
                        wrtr.println();
                    }
                }
            }
        }
    }

    // Build the table, writing csv to wrtr
    private void doCsvStatusTable() throws IOException {
        PrintWriter wrtr = resp.getWriter();
        resp.setContentType("text/plain");

        StatusTable statTable;
        try {
            statTable = makeTable();
        } catch (StatusService.NoSuchTableException e) {
            wrtr.println("No table: " + e.toString());
            return;
        } catch (Exception e) {
            wrtr.println("Error getting table: " + e.toString());
            return;
        }
        java.util.List<ColumnDescriptor> colList = statTable.getColumnDescriptors();
        java.util.List<Map> rowList = getRowList(statTable);
        if (colList != null) {
            for (Iterator colIter = colList.iterator(); colIter.hasNext();) {
                ColumnDescriptor cd = (ColumnDescriptor) colIter.next();
                wrtr.print(StringUtil.csvEncode(cd.getTitle()));
                if (colIter.hasNext()) {
                    wrtr.print(",");
                } else {
                    wrtr.println();
                }
            }
            if (rowList != null) {
                // output rows
                for (Map rowMap : rowList) {
                    for (Iterator colIter = colList.iterator(); colIter.hasNext();) {
                        ColumnDescriptor cd = (ColumnDescriptor) colIter.next();
                        Object val = rowMap.get(cd.getColumnName());
                        Object dispVal = getTextDisplayString(val);
                        String valStr = dispVal != null ? dispVal.toString() : "(null)";
                        wrtr.print(StringUtil.csvEncode(valStr));
                        if (colIter.hasNext()) {
                            wrtr.print(",");
                        } else {
                            wrtr.println();
                        }
                    }
                }
            }
        } else {
            wrtr.println("(Empty table)");
        }
    }

    static final Image UPARROW1 = ServletUtil.image("uparrow1blue.gif", 16, 16, 0,
            "Primary sort column, ascending");
    static final Image UPARROW2 = ServletUtil.image("uparrow2blue.gif", 16, 16, 0,
            "Secondary sort column, ascending");
    static final Image DOWNARROW1 = ServletUtil.image("downarrow1blue.gif", 16, 16, 0,
            "Primary sort column, descending");
    static final Image DOWNARROW2 = ServletUtil.image("downarrow2blue.gif", 16, 16, 0,
            "Secondary sort column, descending");

    /**
     * Create a column heading element:<ul> <li> plain text if not sortable <li>
     * if sortable, link with sortkey set to column name, descending if was
     * previous primary ascending key <li> plus a possible secondary sort key set
     * to previous sort column <li> if is current primary or secondary sort
     * column, display an up or down arrow.</ul>
     *
     * @param statTable
     * @param cd
     * @param rules the SortRules used to sort the currently displayed table
     */
    Composite getColumnTitleElement(StatusTable statTable, ColumnDescriptor cd, java.util.List rules) {
        Composite elem = new Composite();
        Image sortArrow = null;
        boolean ascending = getDefaultSortAscending(cd);
        String colTitle = cd.getTitle();
        if (true && statTable.isResortable() && cd.isSortable()) {
            String ruleParam;
            if (rules != null && !rules.isEmpty()) {
                StatusTable.SortRule rule1 = (StatusTable.SortRule) rules.get(0);
                if (cd.getColumnName().equals(rule1.getColumnName())) {
                    // This column is the current primary sort; link to reverse order
                    ascending = !rule1.sortAscending();
                    // and display a primary arrow
                    sortArrow = rule1.sortAscending() ? UPARROW1 : DOWNARROW1;
                    if (rules.size() > 1) {
                        // keep same secondary sort key if there was one
                        StatusTable.SortRule rule2 = (StatusTable.SortRule) rules.get(1);
                        ruleParam = ruleParam(cd, ascending) + "," + ruleParam(rule2);
                    } else {
                        ruleParam = ruleParam(cd, ascending);
                    }
                } else {
                    if (rules.size() > 1) {
                        StatusTable.SortRule rule2 = (StatusTable.SortRule) rules.get(1);
                        if (cd.getColumnName().equals(rule2.getColumnName())) {
                            // This is the secondary sort column; display secondary arrow
                            sortArrow = rule2.sortAscending() ? UPARROW2 : DOWNARROW2;
                        }
                    }
                    // primary sort is this column, secondary is previous primary
                    ruleParam = ruleParam(cd, ascending) + "," + ruleParam(rule1);
                }
            } else {
                // no previous, sort by column
                ruleParam = ruleParam(cd, ascending);
            }
            Link link = new Link(srvURL(myServletDescr(), modifyParams("sort", ruleParam)), colTitle);
            link.attribute("class", "colhead");
            elem.add(link);
            String foot = cd.getFootnote();
            if (foot != null) {
                elem.add(addFootnote(foot));
            }
            if (sortArrow != null) {
                elem.add(sortArrow);
            }
        } else {
            elem.add(colTitle);
            elem.add(addFootnote(cd.getFootnote()));
        }
        return elem;
    }

    String ruleParam(ColumnDescriptor cd, boolean ascending) {
        return (ascending ? "A" : "D") + cd.getColumnName();
    }

    String ruleParam(StatusTable.SortRule rule) {
        return (rule.sortAscending() ? "A" : "D") + rule.getColumnName();
    }

    java.util.List makeSortRules(StatusTable statTable, String sortKey) {
        Map columnDescriptorMap = statTable.getColumnDescriptorMap();
        java.util.List cols = StringUtil.breakAt(sortKey, ',');
        java.util.List res = new ArrayList();
        for (Iterator iter = cols.iterator(); iter.hasNext();) {
            String spec = (String) iter.next();
            boolean ascending = spec.charAt(0) == 'A';
            String col = spec.substring(1);
            StatusTable.SortRule defaultRule = getDefaultRuleForColumn(statTable, col);
            StatusTable.SortRule rule;
            Comparator comparator = null;
            if (columnDescriptorMap.containsKey(col)) {
                ColumnDescriptor cd = (ColumnDescriptor) columnDescriptorMap.get(col);
                comparator = cd.getComparator();
            }
            if (defaultRule != null && defaultRule.getComparator() != null) {
                comparator = defaultRule.getComparator();
            }
            if (comparator != null) {
                rule = new StatusTable.SortRule(col, comparator, ascending);
            } else {
                rule = new StatusTable.SortRule(col, ascending);
            }
            res.add(rule);
        }
        log.debug2("rules: " + res);
        return res;
    }

    private StatusTable.SortRule getDefaultRuleForColumn(StatusTable statTable, String col) {
        java.util.List defaults = statTable.getDefaultSortRules();
        for (Iterator iter = defaults.iterator(); iter.hasNext();) {
            StatusTable.SortRule rule = (StatusTable.SortRule) iter.next();
            if (col.equals(rule.getColumnName())) {
                return rule;
            }
        }
        return null;
    }

    private boolean getDefaultSortAscending(ColumnDescriptor cd) {
        switch (cd.getType()) {
        case ColumnDescriptor.TYPE_STRING:
        case ColumnDescriptor.TYPE_IP_ADDRESS:
        case ColumnDescriptor.TYPE_TIME_INTERVAL:
        default:
            return true;
        case ColumnDescriptor.TYPE_INT:
        case ColumnDescriptor.TYPE_PERCENT:
        case ColumnDescriptor.TYPE_AGREEMENT:
        case ColumnDescriptor.TYPE_FLOAT: // tk - should align decimal points?
        case ColumnDescriptor.TYPE_DATE:
            return false;
        }
    }

    // Handle lists
    private String getTextDisplayString(Object val) {
        if (val == StatusTable.NO_VALUE) {
            // Some, but not all text formats avoid calling this with NO_VALUE
            return "";
        }
        if (val instanceof java.util.List) {
            StringBuilder sb = new StringBuilder();
            for (Iterator iter = ((java.util.List) val).iterator(); iter.hasNext();) {
                sb.append(StatusTable.getActualValue(iter.next()));
            }
            return sb.toString();
        } else {
            Object dispVal = StatusTable.getActualValue(val);
            return dispVal != null ? dispVal.toString() : "(null)";
        }
    }

    // Handle lists
    private String getDisplayString(Object val, int type) {
        if (val instanceof java.util.List) {
            StringBuilder sb = new StringBuilder();
            for (Iterator iter = ((java.util.List) val).iterator(); iter.hasNext();) {
                sb.append(getDisplayString0(iter.next(), type));
            }
            return sb.toString();
        } else {
            return getDisplayString0(val, type);
        }
    }

    // Process References and other links
    private String getDisplayString0(Object val, int type) {
        if (val instanceof StatusTable.Reference) {
            return getRefString((StatusTable.Reference) val, type);
        } else if (val instanceof StatusTable.SrvLink) {
            // Display as link iff user is allowed access to the target servlet
            StatusTable.SrvLink slink = (StatusTable.SrvLink) val;
            if (isServletRunnable(slink.getServletDescr())) {
                return getSrvLinkString(slink, type);
            } else {
                return getDisplayString1(StatusTable.getActualValue(val), type);
            }
        } else if (val instanceof StatusTable.LinkValue) {
            // A LinkValue type we don't know about.  Just display its embedded
            // value.
            return getDisplayString1(StatusTable.getActualValue(val), type);
        } else {
            return getDisplayString1(val, type);
        }
    }

    // turn References into html links
    private String getRefString(StatusTable.Reference ref, int type) {
        StringBuilder sb = new StringBuilder();
        sb.append("table=");
        sb.append(ref.getTableName());
        String key = ref.getKey();
        if (!StringUtil.isNullString(key)) {
            sb.append("&key=");
            sb.append(urlEncode(key));
        }
        Properties refProps = ref.getProperties();
        if (refProps != null) {
            for (Iterator iter = refProps.entrySet().iterator(); iter.hasNext();) {
                Map.Entry ent = (Map.Entry) iter.next();
                sb.append("&");
                sb.append(ent.getKey());
                sb.append("=");
                sb.append(urlEncode((String) ent.getValue()));
            }
        }
        if (ref.getPeerId() != null) {
            return srvAbsLink(ref.getPeerId(), myServletDescr(), getDisplayString(ref.getValue(), type),
                    sb.toString());
        } else {
            return srvLink(myServletDescr(), getDisplayString(ref.getValue(), type), sb.toString());
        }
    }

    // turn UrlLink into html link
    private String getSrvLinkString(StatusTable.SrvLink link, int type) {
        return srvLink(link.getServletDescr(), getDisplayString1(link.getValue(), type), link.getArgs());
    }

    // add display attributes from a DisplayedValue
    private String getDisplayString1(Object val, int type) {
        if (val instanceof StatusTable.DisplayedValue) {
            StatusTable.DisplayedValue aval = (StatusTable.DisplayedValue) val;
            String str = aval.hasDisplayString() ? HtmlUtil.htmlEncode(aval.getDisplayString())
                    : getDisplayString1(aval.getValue(), type);
            String color = aval.getColor();
            String footnote = aval.getFootnote();
            if (color != null) {
                str = "<font color=" + color + ">" + str + "</font>";
            }
            if (aval.getBold()) {
                str = "<b>" + str + "</b>";
            }
            if (footnote != null) {
                str = str + addFootnote(footnote);
            }
            return str;
        } else {
            String str = convertDisplayString(val, type);
            if (type == ColumnDescriptor.TYPE_STRING) {
                str = HtmlUtil.htmlEncode(str);
            }
            return str;
        }
    }

    // Thread-safe formatters.
    // FastDateFormat is thread-safe, NumberFormat & subclasses aren't.
    /**
     * Format to display date/time in tables
     */
    private static final Format tableDf = FastDateFormat.getInstance("HH:mm:ss MM/dd/yy");

    //   public static final DateFormat tableDf =
    //     DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
    static Format getTableDateFormat() {
        return tableDf;
    }

    private static final ThreadLocal<NumberFormat> agmntFmt = new ThreadLocal<NumberFormat>() {
        @Override
        protected NumberFormat initialValue() {
            return new DecimalFormat("0.00");
        }
    };

    static NumberFormat getAgreementFormat() {
        return agmntFmt.get();
    }

    private static final ThreadLocal<NumberFormat> floatFmt = new ThreadLocal<NumberFormat>() {
        @Override
        protected NumberFormat initialValue() {
            return new DecimalFormat("0.0");
        }
    };

    static NumberFormat getFloatFormat() {
        return floatFmt.get();
    }

    private static final ThreadLocal<NumberFormat> bigIntFmt = new ThreadLocal<NumberFormat>() {
        @Override
        protected NumberFormat initialValue() {
            NumberFormat fmt = NumberFormat.getInstance();
            //       if (fmt instanceof DecimalFormat) {
            //         ((DecimalFormat)fmt).setDecimalSeparatorAlwaysShown(true);
            //       }
            return fmt;
        }
    };

    static NumberFormat getBigIntFormat() {
        return bigIntFmt.get();
    }

    /* DecimalFormat automatically applies half-even rounding to
     * values being formatted under Java < 1.6.  This is a workaround. */
    private static String doubleToPercent(double d) {
        int i = (int) (d * 10000);
        double pc = i / 100.0;
        return getAgreementFormat().format(pc);
    }

    // turn a value into a display string
    public static String convertDisplayString(Object val, int type) {
        if (val == null) {
            return "";
        }
        try {
            switch (type) {
            case ColumnDescriptor.TYPE_INT:
                if (val instanceof Number) {
                    long lv = ((Number) val).longValue();
                    if (lv >= 1000000) {
                        return getBigIntFormat().format(lv);
                    }
                }
                // fall thru
            case ColumnDescriptor.TYPE_STRING:
            default:
                return val.toString();
            case ColumnDescriptor.TYPE_FLOAT:
                return getFloatFormat().format(((Number) val).doubleValue());
            case ColumnDescriptor.TYPE_PERCENT:
                float fv = ((Number) val).floatValue();
                return Integer.toString(Math.round(fv * 100)) + "%";
            case ColumnDescriptor.TYPE_AGREEMENT:
                float av = ((Number) val).floatValue();
                return doubleToPercent(av) + "%";
            case ColumnDescriptor.TYPE_DATE:
                Date d;
                if (val instanceof Number) {
                    d = new Date(((Number) val).longValue());
                } else if (val instanceof Date) {
                    d = (Date) val;
                } else if (val instanceof Deadline) {
                    d = ((Deadline) val).getExpiration();
                } else {
                    return val.toString();
                }
                return dateString(d);
            case ColumnDescriptor.TYPE_IP_ADDRESS:
                return ((IPAddr) val).getHostAddress();
            case ColumnDescriptor.TYPE_TIME_INTERVAL:
                long millis = ((Number) val).longValue();
                return StringUtil.timeIntervalToString(millis);
            }
        } catch (NumberFormatException e) {
            log.warning("Bad number: " + val.toString(), e);
            return val.toString();
        } catch (ClassCastException e) {
            log.warning("Wrong type value: " + val.toString(), e);
            return val.toString();
        } catch (Exception e) {
            log.warning("Error formatting value: " + val.toString(), e);
            return val.toString();
        }
    }

    public static String dateString(Date d) {
        long val = d.getTime();
        if (val == 0 || val == -1) {
            return "never";
        } else {
            return getTableDateFormat().format(d);
        }
    }

    private static BitSet debugOptions = new BitSet();

    static {
        debugOptions.set(StatusTable.OPTION_DEBUG_USER);
    }

    protected boolean isAllTablesTable() {
        return StatusService.ALL_TABLES_TABLE.equals(tableName);
    }

    // make me a link in nav table unless I'm displaying table of all tables
    protected boolean linkMeInNav() {
        return !isAllTablesTable();
    }
}