@RequestMapping(value = "/info/", method = RequestMethod.GET)
  public void showInfo(ModelMap model) throws Exception {

    Client client = clientProvider.getThriftClient();

    model.put("clusterName", client.describe_cluster_name());
    model.put("version", client.describe_version());

    /*
    NodeProbe probe = clientProvider.getProbe();
    model.put("liveNodes", probe.getLiveNodes());
    model.put("unreachableNodes", probe.getUnreachableNodes());

    model.put("uptime", getUptimeString(probe.getUptime()));
    model.put("token", probe.getToken());
    */
    model.put("menu_info", Boolean.TRUE);
  }
  /**
   * Show ring
   *
   * @param model
   * @throws Exception
   */
  @SuppressWarnings({"rawtypes", "unchecked"})
  @RequestMapping(value = "/ring/", method = RequestMethod.GET)
  public void describeRing(ModelMap model) throws Exception {

    NodeProbe probe = clientProvider.getProbe();
    if (probe == null) {
      // TODO JMX Connection failed
      throw new RuntimeException("JMX Connection failed.");
    }

    Set<String> liveNodes = probe.getLiveNodes();
    Set<String> deadNodes = probe.getUnreachableNodes();
    Set<String> joiningNodes = probe.getJoiningNodes();
    Set<String> leavingNodes = probe.getLeavingNodes();
    Map<String, String> loadMap = probe.getLoadMap();

    Map<Token, String> endpointMap = probe.getTokenToEndpointMap();
    List<Node> nodes = new ArrayList<Node>(endpointMap.size());
    List<Token> sortedTokens = new ArrayList<Token>(endpointMap.keySet());
    Collections.sort(sortedTokens);

    for (Object token : sortedTokens) {

      String primaryEndpoint = endpointMap.get(token);

      Node node = new Node();
      node.address = primaryEndpoint;
      node.token = token.toString();
      node.load = loadMap.get(node.address);
      node.up =
          liveNodes.contains(primaryEndpoint)
              ? "up"
              : deadNodes.contains(primaryEndpoint) ? "down" : "?";

      node.state =
          joiningNodes.contains(primaryEndpoint)
              ? "Joining"
              : leavingNodes.contains(primaryEndpoint) ? "Leaving" : "Normal";

      if (node.load == null) {
        node.load = "?";
      }
      nodes.add(node);

      NodeProbe inProbe = clientProvider.getProbe(node.address);
      if (inProbe != null) {
        node.operationMode = inProbe.getOperationMode();
        node.uptime = getUptimeString(inProbe.getUptime());
        node.jmx = true;

        MemoryUsage memory = inProbe.getHeapMemoryUsage();

        node.memoryUsed = String.format("%.2f MB", (double) memory.getUsed() / (1024 * 1024));
        node.memoryMax = String.format("%.2f MB", (double) memory.getMax() / (1024 * 1024));
        node.memoryCommited =
            String.format("%.2f MB", (double) memory.getCommitted() / (1024 * 1024));
      }
    }

    // List live nodes which are not in range.
    for (String deadAddress : deadNodes) {
      Node deadNode = new Node();
      deadNode.address = deadAddress;
      deadNode.load = loadMap.get(deadAddress);
      NodeProbe inProbe = clientProvider.getProbe(deadAddress);
      if (inProbe != null) {
        deadNode.operationMode = inProbe.getOperationMode();
        deadNode.uptime = getUptimeString(inProbe.getUptime());
      }
    }

    model.put("nodes", nodes);
    model.put("menu_ring", Boolean.TRUE);
  }
  /**
   * Show node statistics
   *
   * @param address
   * @param model
   * @return
   */
  @RequestMapping(value = "/ring/{address}/", method = RequestMethod.GET)
  public String describeNode(@PathVariable("address") String address, ModelMap model) {

    NodeProbe probe = clientProvider.getProbe(address);

    model.addAttribute("address", address);
    model.addAttribute("token", probe.getToken());
    model.addAttribute("mode", probe.getOperationMode());
    model.addAttribute("uptime", getUptimeString(probe.getUptime()));

    // Column family store
    probe.getColumnFamilyStoreMBeanProxies();
    Iterator<Entry<String, ColumnFamilyStoreMBean>> iterator =
        probe.getColumnFamilyStoreMBeanProxies();

    Map<String, Map<String, ColumnFamilyStoreMBean>> cfparent =
        new TreeMap<String, Map<String, ColumnFamilyStoreMBean>>();

    while (iterator.hasNext()) {
      Entry<String, ColumnFamilyStoreMBean> entry = iterator.next();
      String keyspace = entry.getKey();
      String columnFamily = entry.getValue().getColumnFamilyName();

      Map<String, ColumnFamilyStoreMBean> cfmap = cfparent.get(keyspace);
      if (cfmap == null) {
        cfmap = new TreeMap<String, ColumnFamilyStoreMBean>();
        cfparent.put(keyspace, cfmap);
      }
      cfmap.put(columnFamily, entry.getValue());
    }

    // Thread pool stats
    Iterator<Entry<String, IExecutorMBean>> tpIterator = probe.getThreadPoolMBeanProxies();
    Map<String, IExecutorMBean> tpMap = new TreeMap<String, IExecutorMBean>();
    while (tpIterator.hasNext()) {
      Entry<String, IExecutorMBean> entry = tpIterator.next();
      tpMap.put(entry.getKey(), entry.getValue());
    }

    model.addAttribute("cfparent", cfparent);
    model.addAttribute("tpmap", tpMap);
    model.addAttribute("address", address);
    model.addAttribute("menu_ring", true);

    // TODO not implemented yet
    //		model.addAttribute("streamDestinations", probe.getStreamDestinations());
    //		model.addAttribute("streamSources", probe.getStreamSources());
    model.addAttribute("currentGenerationNumber", probe.getCurrentGenerationNumber());

    /*
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    PrintStream ps = new PrintStream(bout);
    probe.getCompactionThreshold(ps, null, null);
    ps.flush();
    String compactionThreshold = new String(bout.toByteArray());
    if (compactionThreshold.startsWith("Current compaction threshold: ")) {
    	compactionThreshold = compactionThreshold.substring(30);
    }
    model.addAttribute("compactionThreshold", compactionThreshold);
    */

    return "/ring_node";
  }