/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.servlets;

import com.caucho.VersionFactory;
import com.caucho.config.ConfigException;
import com.caucho.jmx.Jmx;
import com.caucho.management.server.*;
import com.caucho.util.L10N;
import com.caucho.util.QDate;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Displays some status information about the Resin server.
 * The servlet must be explicitly enabled (using /servlet is forbidden),
 * and it must have the init-param enable set to "read" or "write".
 * (There will likely be a future additional requirement of satisfying
 * a role.)
 */
public class ResinStatusServlet extends GenericServlet {
  static final protected Logger log
    = Logger.getLogger(ResinStatusServlet.class.getName());
  static final L10N L = new L10N(ResinStatusServlet.class);

  private static final long SECOND = 1000L;
  private static final long MINUTE = 60 * SECOND;
  private static final long HOUR = 60 * MINUTE;
  private static final long DAY = 24 * HOUR;

  private String _enable;

  private MBeanServer _mbeanServer;
  private ResinMXBean _resin;
  private ServerMXBean _server;
  private ClusterMXBean _cluster;
  private ProxyCacheMXBean _proxyCache;

  /**
   * Set to read or write.
   */
  public void setEnable(String enable)
    throws ConfigException
  {
    if ("read".equals(enable) || "write".equals(enable))
      _enable = enable;
    else
      throw new ConfigException(L.l("enable value '{0}' must either be read or write.",
                                    enable));
  }

  /**
   * Initialize the servlet with the server's sruns.
   */
  public void init()
    throws ServletException
  {
    if (_enable == null)
      throw new ServletException(L.l("ResinStatusServlet requires an explicit enable attribute."));

    try {
      //_mbeanServer = (MBeanServer) new InitialContext().lookup("java:comp/jmx/GlobalMBeanServer");

      // _mbeanServer = Jmx.findMBeanServer();

      // _mbeanServer = Jmx.getMBeanServer();
      _mbeanServer =
        Jmx.getGlobalMBeanServer();

      //_resinServer = (ResinServerMBean) Jmx.find("resin:type=ResinServer");
      //_servletServer = (ServletServerMBean) Jmx.find("resin:name=default,type=Server");

      _resin = (ResinMXBean) Jmx.findGlobal("resin:type=Resin");
      _server = (ServerMXBean) Jmx.findGlobal("resin:type=Server");
      _cluster = (ClusterMXBean) Jmx.findGlobal("resin:type=Cluster");
      _proxyCache = (ProxyCacheMXBean) Jmx.findGlobal("resin:type=ProxyCache");
    } catch (Exception e) {
      throw new ServletException(e);
    }
  }

  /**
   * Handle the request.
   */
  public void service(ServletRequest request, ServletResponse response)
    throws IOException, ServletException
  {
    try {
      HttpServletRequest req = (HttpServletRequest) request;
      HttpServletResponse res = (HttpServletResponse) response;

      res.setContentType("text/html");

      Thread thread = Thread.currentThread();
      thread.setContextClassLoader(ClassLoader.getSystemClassLoader());

      PrintWriter out = res.getWriter();

      printHeader(out);

      String hostName = req.getParameter("host");
      String appName = req.getParameter("app");

      printServerHeader(out);
      printPorts(out);
      printSrun(out);
      /*
        printJNDI(out, _server.getJndiContext());
      */
      printApplicationSummary(out, req.getRequestURI());
      printFooter(out);
    } catch (IOException e) {
      throw e;
    } catch (ServletException e) {
      throw e;
    } catch (Exception e) {
      throw new ServletException(e);
    }
  }

    /*
  private void printApplication(PrintWriter out,
                                ApplicationAdmin app,
                                String pwd)
    throws IOException, ServletException
  {
    ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();

    try {
      Thread.currentThread().setContextClassLoader(app.getClassLoader());

      printHeader(out);

      printApplicationHeader(out, app, pwd);
      printJNDI(out, app.getJndiContext());

      printJMXServlets(out, app.getMBeanServer());

      printFooter(out);
    } finally {
      Thread.currentThread().setContextClassLoader(oldLoader);
    }
  }
    */

  /**
   * Prints generic server information.
   */
  public void printHeader(PrintWriter out)
    throws IOException, ServletException
  {
  }

  /**
   * Prints server information.
   */
  public void printServerHeader(PrintWriter out)
    throws Exception
  {
    out.println("<b>resin-status</b><br><br>");

    String id = _server.getId();

    out.println("<table border=\"0\">");
    if (id != null)
      out.println("<tr><td><b>Server:</b><td>" + id);

    String configFile = _resin.getConfigFile();
    if (configFile != null)
      out.println("<tr><td><b>Config:</b><td>" + configFile);

    long startTime = _server.getStartTime().getTime();

    out.println("<tr><td><b>Server Start:</b><td> " +
                QDate.formatLocal(startTime));

    long totalMemory = _server.getRuntimeMemory();
    out.println("<tr><td><b>Total Memory:</b><td> " +
                (totalMemory / 1000000) + "." +
                (totalMemory / 100000) % 10 +
                (totalMemory / 10000) % 10 +
                "Meg");
    long freeMemory = _server.getRuntimeMemoryFree();
    out.println("<tr><td><b>Free Memory:</b><td> " +
                (freeMemory / 1000000) + "." +
                (freeMemory / 100000) % 10 +
                (freeMemory / 10000) % 10 +
                "Meg");
    
    long invocationHitCount = _server.getInvocationCacheHitCountTotal();
    long invocationMissCount = _server.getInvocationCacheMissCountTotal();

    long totalCount = invocationHitCount + invocationMissCount;
    if (totalCount == 0)
      totalCount = 1;

    long hitRatio = (10000 * invocationHitCount) / totalCount;

    out.print("<tr><td><b>Invocation Hit Ratio:</b><td> " +
              (hitRatio / 100) + "." +
              (hitRatio / 10) % 10 +
              (hitRatio) % 10 + "%");
    out.println(" (" + invocationHitCount + "/" + totalCount + ")");

    if (_proxyCache != null) {
      long proxyHitCount = _proxyCache.getHitCountTotal();
      long proxyMissCount = _proxyCache.getMissCountTotal();

      totalCount = proxyHitCount + proxyMissCount;
      if (totalCount == 0)
        totalCount = 1;

      hitRatio = (10000 * proxyHitCount) / totalCount;

      out.print("<tr><td><b>Proxy Cache Hit Ratio:</b><td> " +
                (hitRatio / 100) + "." +
                (hitRatio / 10) % 10 +
                (hitRatio) % 10 + "%");
      out.println(" (" + proxyHitCount + "/" + totalCount + ")");
    }

    out.println("</table>");

    printThreadHeader(out);
    printConnectionPools(out, "");
  }

  /**
   * Prints thread information.
   */
  public void printThreadHeader(PrintWriter out)
    throws Exception
  {
    out.println("<table border='3'>");

    ThreadPoolMXBean threadPool = (ThreadPoolMXBean) Jmx.findGlobal("resin:type=ThreadPool");
    out.println("<tr><th colspan='3'>Threads");
    out.println("    <th colspan='3'>Config");
    out.println("<tr><th>Active<th>Idle<th>Total");
    out.println("    <th>thread-max<th>thread-idle-min");
    out.println("<tr align='right'>");
    out.println("    <td>" + threadPool.getThreadActiveCount());
    out.println("    <td>" + threadPool.getThreadIdleCount());
    out.println("    <td>" + threadPool.getThreadCount());

    out.println("    <td>" + threadPool.getThreadMax());
    out.println("    <td>" + threadPool.getThreadIdleMin());

    out.println("</table>");
  }

  /**
   * Prints application information.
   */
  /*
  public void printApplicationHeader(PrintWriter out,
                                     ApplicationAdmin app,
                                     String pwd)
    throws IOException, ServletException
  {
    HostAdmin host = app.getHostAdmin();

    out.println("<b><a href=\"" + pwd + "\">resin-status</a> > ");
    out.println("<a href=\"" + pwd +
                "?host=" + host.getName() + "\">" + host.getURL() + "</a> > ");
    out.println(app.getContextPath() + "</b><br><br>");

    String id = _server.getServerId();

    out.println("<table border=\"0\">");
    if (id != null)
      out.println("<tr><td><b>Server:</b><td>" + id);
    Path config = _server.getConfig();
    if (config != null)
      out.println("<tr><td><b>Config:</b><td>" + config.getNativePath());

    out.println("<tr><td><b>Host:</b><td>" + app.getHostAdmin().getURL());
    out.println("<tr><td><b>Web-App:</b><td>" + app.getContextPath());
    out.println("<tr><td><b>App-Dir:</b><td>" + app.getAppDir().getNativePath());

    long startTime = _server.getStartTime();
    long restartTime = app.getStartTime();

    out.println("<tr><td><b>Server Start:</b><td>" + QDate.formatLocal(startTime));
    out.println("<tr><td><b>Web-App Start:</b><td> " + QDate.formatLocal(restartTime));
    long totalMemory = Runtime.getRuntime().totalMemory();
    out.println("<tr><td><b>Total Memory:</b><td> " +
                (totalMemory / 1000000) + "." +
                (totalMemory / 100000) % 10 +
                (totalMemory / 10000) % 10 +
                "Meg");
    long freeMemory = Runtime.getRuntime().freeMemory();
    out.println("<tr><td><b>Free Memory:</b><td> " +
                (freeMemory / 1000000) + "." +
                (freeMemory / 100000) % 10 +
                (freeMemory / 10000) % 10 +
                "Meg");

    out.println("<tr><td><b>Sessions:</b><td>" +
                app.getActiveSessionCount());

    out.println("</table>");
  }
  */

  public void printPorts(PrintWriter out)
    throws IOException, ServletException
  {
    try {
      PortMXBean []portList = _server.getPorts();

      if (portList.length > 0) {
        out.println("<h3>TCP ports</h3>");
        out.println("<table border='2'>");
        out.println("<tr><th><th colspan='3'>Threads<th>&nbsp;");
        out.println("<tr><th>Protocol:Port");
        out.println("    <th>Active<th>Idle<th>Total");
        out.println("    <th>Keepalive<th>Select");

        for (int i = 0; i < portList.length; i++) {
          PortMXBean port = portList[i];

          if (port == null || ! "active".equals(port.getState()))
            continue;

          String host = port.getAddress();
          if (host == null)
            host = "*";

          out.print("<tr><td>");
          out.print(port.getProtocolName() + "://" + host + ":" + port.getPort());
          out.println();

          out.print("    <td>" + port.getThreadActiveCount());
          out.print("<td>" + port.getThreadIdleCount());
          out.print("<td>" + port.getThreadActiveCount());
          out.print("<td>" + port.getKeepaliveThreadCount());
          out.print("<td>" + port.getKeepaliveSelectCount());
          out.println();

          out.println();
        }
        out.println("</table>");
      }
    } catch (Exception e) {
      throw new ServletException(e);
    }
  }

  public void printSrun(PrintWriter out)
    throws IOException, ServletException
  {
    try {
      String[]clusterList = new String[0]; // _server.getClusterObjectNames();

      for (int i = 0; i < clusterList.length; i++) {
        ClusterMXBean cluster = (ClusterMXBean) Jmx.findGlobal(clusterList[i]);

        if (cluster == null) {
          out.println("<h3>Cluster " + clusterList[i] + " null</h3>");
          continue;
        }

        ObjectName objectName = cluster.getObjectName();

        String clusterName = objectName.getKeyProperty("name");

        out.println("<h3>Cluster " + clusterName + "</h3>");
        out.println("<table border='2'>");
        out.println("<tr><th>Host");
        out.println("    <th>Active");

        ClusterServerMXBean []servers = cluster.getServers();

        for (int j = 0; j < servers.length; j++) {
          ClusterServerMXBean client = servers[j];

          String host = client.getAddress();
          String port = String.valueOf(client.getPort());

          out.println("<tr>");

          boolean canConnect = client.ping();

          if (canConnect)
            out.print("<td bgcolor='#80ff80'>");
          else
            out.print("<td>");

          out.print(host + ":" + port);

          if (canConnect)
            out.println(" (up)");
          else
            out.println(" (down)");

          out.println("<td>" + client.getConnectionActiveCount());
        }

        out.println("</table>");
      }
    } catch (Exception e) {
      throw new ServletException(e);
    }
  }

  public void printConnectionPools(PrintWriter out, String context)
    throws Exception
  {
    ObjectName pattern = new ObjectName("resin:*,type=ConnectionPool" + context);

    Set<ObjectName> poolNames;
    poolNames = _mbeanServer.queryNames(pattern, null);

    if (poolNames.size() == 0)
      return;

    out.println("<h3>Connection Pools</h3>");

    out.println("<table border='2'>");
    out.println("<tr><th>&nbsp;<th colspan='3'>Connections<th colspan='2'>Config");
    out.println("<tr><th>Name<th>Active<th>Idle<th>Total");
    out.println("    <th>max-connections<th>idle-time");

    Iterator<ObjectName> iter = poolNames.iterator();
    while (iter.hasNext()) {
      ObjectName name = iter.next();

      ConnectionPoolMXBean pool = (ConnectionPoolMXBean) Jmx.findGlobal(name);

      if (pool != null) {
        out.println("<tr><td>" + pool.getName());
        out.println("    <td>" + pool.getConnectionActiveCount());
        out.println("    <td>" + pool.getConnectionIdleCount());
        out.println("    <td>" + pool.getConnectionCount());

        out.println("    <td>" + pool.getMaxConnections());
        out.println("    <td>" + periodToString(pool.getMaxIdleTime()));
      }
    }

    out.println("</table>");
  }

  private String periodToString(long time)
  {
    if (time == 0)
      return "0s";
    else if (time % DAY == 0)
      return (time / DAY) + "d";
    else if (time % HOUR == 0)
      return (time / HOUR) + "h";
    else if (time % MINUTE == 0)
      return (time / MINUTE) + "min";
    else if (time % SECOND == 0)
      return (time / SECOND) + "s";
    else
      return time + "ms";
  }

  /*
  public void printSrun(PrintWriter out)
    throws IOException, ServletException
  {
    DistributionServer []srunList = _server.getDistributionServerList();

    if (srunList == null || srunList.length == 0)
      return;

    out.println("<h3>Srun Servers</h3>");
    out.println("<table border=2>");
    out.println("<tr><th>Host</th><th>Active Count</th><th>live-time</th><th>dead-time</th><th>request-timeout</th>");

    for (int i = 0; i < srunList.length; i++) {
      DistributionServer srun = srunList[i];
      out.print("<tr>");

      boolean isLive = false;
      try {
        ReadWritePair pair = srun.open();
        if (pair != null) {
          isLive = true;
          srun.free(pair);
        }
      } catch (Throwable e) {
        dbg.log(e);
      }

      if (isLive) {
        out.println("<td bgcolor=\"#66ff66\">" +
                    (srun.getIndex() + 1) + ". " +
                    srun.getHost() + ":" + srun.getPort() +
                    (srun.isBackup() ? "*" : "") +
                    " (ok)");
      }
      else {
        out.println("<td bgcolor=\"#ff6666\">" +
                    (srun.getIndex() + 1) + ". " +
                    srun.getHost() + ":" + srun.getPort() +
                    (srun.isBackup() ? "*" : "") +
                    " (down)");
      }
      out.println("<td>" + srun.getActiveCount());
      out.println("<td>" + srun.getLiveTime() / 1000);
      out.println("<td>" + srun.getDeadTime() / 1000);
      out.println("<td>" + srun.getTimeout() / 1000);
    }
    out.println("</table>");
  }

  public void printJNDI(PrintWriter out, Context ic)
    throws IOException, ServletException
  {
    printDatabasePools(out, ic);
    printEJBLocalHomes(out, ic);
    printEJBRemoteHomes(out, ic);
  }

  public void printEJBLocalHomes(PrintWriter out, Context ic)
    throws IOException, ServletException
  {
    try {
      Context cmpCxt = (Context) ic.lookup("java:comp/env/cmp");

      if (cmpCxt == null)
        return;

      NamingEnumeration list = cmpCxt.list("");

      ArrayList cmpNames = new ArrayList();
      while (list.hasMoreElements()) {
        NameClassPair pair = (NameClassPair) list.nextElement();

        cmpNames.add(pair.getName());
      }

      if (cmpNames.size() == 0)
        return;

      out.println("<h3>EJB Local Home</h3>");

      out.println("<table border=\"2\">");
      out.println("<tr><th>Name<th>Home Stub Class");

      Collections.sort(cmpNames);

      for (int i = 0; i < cmpNames.size(); i++) {
        String name = (String) cmpNames.get(i);

        Object value = cmpCxt.lookup(name);
        if (! (value instanceof EJBLocalHome))
          continue;

        EJBLocalHome home = (EJBLocalHome) value;
        out.print("<tr><td>cmp/" + name);
        Class homeStub = home.getClass();
        Class []interfaces = home.getClass().getInterfaces();
        for (int j = 0; j < interfaces.length; j++) {
          if (EJBLocalHome.class.isAssignableFrom(interfaces[j])) {
            homeStub = interfaces[j];
            break;
          }
        }
        out.print("<td>" + homeStub.getName());
      }
      out.println("</table>");
    } catch (Exception e) {
      dbg.log(e);
    }
  }

  public void printEJBRemoteHomes(PrintWriter out, Context ic)
    throws IOException, ServletException
  {
    try {
      Context ejbCxt = (Context) ic.lookup("java:comp/env/ejb");

      if (ejbCxt == null)
        return;

      NamingEnumeration list = ejbCxt.list("");

      ArrayList ejbNames = new ArrayList();
      while (list.hasMoreElements()) {
        NameClassPair pair = (NameClassPair) list.nextElement();

        ejbNames.add(pair.getName());
      }

      if (ejbNames.size() == 0)
        return;

      out.println("<h3>EJB Home</h3>");

      out.println("<table border=\"2\">");
      out.println("<tr><th>Name<th>Home Stub Class");

      Collections.sort(ejbNames);

      for (int i = 0; i < ejbNames.size(); i++) {
        String name = (String) ejbNames.get(i);

        Object value = ejbCxt.lookup(name);
        if (! (value instanceof EJBHome))
          continue;

        EJBHome home = (EJBHome) value;
        out.print("<tr><td>ejb/" + name);
        Class homeStub = home.getClass();
        Class []interfaces = home.getClass().getInterfaces();
        for (int j = 0; j < interfaces.length; j++) {
          if (EJBHome.class.isAssignableFrom(interfaces[j])) {
            homeStub = interfaces[j];
            break;
          }
        }
        out.print("<td>" + homeStub.getName());
      }
      out.println("</table>");
    } catch (Exception e) {
      dbg.log(e);
    }
  }

  public void printJMXServlets(PrintWriter out, MXBeanServer server)
    throws IOException, ServletException
  {
    try {
      ObjectName queryName = new ObjectName("*:j2eeType=Servlet,*");

      Set servlets = server.queryNames(queryName, null);

      if (servlets.size() == 0)
        return;

      out.println("<h3>Servlets</h3>");

      Iterator iter = servlets.iterator();
      while (iter.hasNext()) {
        ObjectName servletName = (ObjectName) iter.next();

        String name = servletName.getKeyProperty("name");
        MXBeanInfo mbeanInfo = server.getMXBeanInfo(servletName);
        MXBeanAttributeInfo []attrs = mbeanInfo.getAttributes();

        out.println("<table border=\"2\">");
        out.print("<tr><th>Name</th>");

        for (int i = 0; i < attrs.length; i++)
          out.print("<th>" + attrs[i].getName() + "</th>");
        out.println("</tr>");

        out.print("<tr><td>" + name + "</td>");
        for (int i = 0; i < attrs.length; i++) {
          Object value = server.getAttribute(servletName, attrs[i].getName());

          out.print("<td>" + value + "</td>");
        }

        out.println("</table>");
      }
    } catch (Exception e) {
      dbg.log(e);
    }
  }
  */

  public void printApplicationSummary(PrintWriter out, String pwd)
    throws Exception
  {
    out.println("<h3>Hosts and Applications</h3>");

    out.println("<table border=\"2\">");
    out.println("<tr><th>Host<th>Web-App<th>State<th>Sessions");

    ObjectName hostPattern = new ObjectName("resin:*,type=Host");

    Set<ObjectName> names = _mbeanServer.queryNames(hostPattern, null);
    Iterator<ObjectName> iter = names.iterator();

    ArrayList<HostMXBean> hosts = new ArrayList<HostMXBean>();

    while (iter.hasNext()) {
      ObjectName name = iter.next();

      // the Host with name=current is a duplicate
      if ("current".equals(name.getKeyProperty("name")))
        continue;

      HostMXBean host = (HostMXBean) Jmx.findGlobal(name);

      if (host != null) {
        hosts.add(host);
      }
    }

    Collections.sort(hosts, new HostCompare());

    for (int i = 0; i < hosts.size(); i++) {
      HostMXBean host = hosts.get(i);

      out.println("<tr><td><b>" + host.getURL() + "</b>");

      // thread.setContextClassLoader(hostLoader);

      String hostName = host.getHostName();
      if (hostName.equals(""))
        hostName = "default";

      ObjectName appPattern = new ObjectName("resin:*,Host=" + hostName + ",type=WebApp");

      names = _mbeanServer.queryNames(appPattern, null);
      iter = names.iterator();

      ArrayList<WebAppMXBean> apps = new ArrayList<WebAppMXBean>();

      while (iter.hasNext()) {
        ObjectName name = iter.next();

        try {
          WebAppMXBean app = (WebAppMXBean) Jmx.findGlobal(name);

          if (app != null)
            apps.add(app);
        } catch (Exception e) {
          log.log(Level.WARNING, e.toString());
          out.println("<tr><td>" + name + "<td>" + e.toString());
        }
      }

      Collections.sort(apps, new AppCompare());

      for (int j = 0; j < apps.size(); j++) {
        WebAppMXBean webApp = apps.get(j);
        SessionManagerMXBean session = webApp.getSessionManager();

        String contextPath = webApp.getContextPath();

        if (contextPath.equals(""))
          contextPath = "/";

        out.print("<tr><td><td>");
        out.print("<a href=\"" + pwd + "?host=" + host.getHostName() +
                  "&app=" + webApp.getContextPath() + "\">");
        out.print(contextPath);
        out.print("</a>");

        String state = webApp.getState();
        if (state.equals("active"))
          out.print("<td bgcolor='#80ff80'>" + webApp.getState());
        else
          out.print("<td>" + webApp.getState());
        
        if (session != null)
          out.print("<td>" + session.getSessionActiveCount());
        else
          out.print("<td>n/a");
      }
    }

    out.println("</table>");
  }

  public void printVirtualHosts(PrintWriter out)
    throws IOException, ServletException
  {
  }

  /**
   * Prints footer information.
   */
  public void printFooter(PrintWriter out)
    throws IOException, ServletException
  {
    /*
    if (_server.isTesting())
      out.println("<br><em>Resin test</em>");
    else
    */
      out.println("<br><em>" + VersionFactory.getFullVersion() + "</em>");
  }

  static class HostCompare implements Comparator<HostMXBean> {
    public int compare(HostMXBean a, HostMXBean b)
    {
      String urlA = a.getURL();
      String urlB = b.getURL();

      if (urlA == urlB)
        return 0;
      else if (urlA == null)
        return -1;
      else if (urlB == null)
        return 1;
      else
        return urlA.compareTo(urlB);
    }
  }

  static class AppCompare implements Comparator<WebAppMXBean> {
    public int compare(WebAppMXBean a, WebAppMXBean b)
    {
      String cpA = a.getContextPath();
      String cpB = b.getContextPath();

      return cpA.compareTo(cpB);
    }
  }
}
