public String render(ReportNode node) {
    if (node != null) {
      StringBuffer text = new StringBuffer();
      text.append("<r c=\"")
          .append(StringEscapeUtils.escapeXml(node.getClass().getSimpleName()))
          .append("\" ");

      String icon = node.getIcon();
      if (icon != null) {
        text.append("i=\"").append(StringEscapeUtils.escapeXml(icon)).append("\" ");
      }

      if (node.getDate() != null) {
        text.append("t=\"").append("" + node.getDate().getTime()).append("\" ");
      }

      text.append("l=\"").append(StringEscapeUtils.escapeXml(node.getLevel())).append("\" ");

      text.append("d=\"").append(node.getDebug()).append("\" ");

      text.append(">");
      if (node.getTitle() != null) {
        text.append("<t>").append(StringEscapeUtils.escapeXml(node.getTitle())).append("</t>");
      }

      if (node.getHint() != null) {
        text.append("<h>").append(StringEscapeUtils.escapeXml(node.getHint())).append("</h>");
      }

      if (node instanceof TextReportNode) {
        TextReportNode textNode = (TextReportNode) node;
        if (textNode.getDetails() != null) {
          text.append("<d>")
              .append(StringEscapeUtils.escapeXml(textNode.getDetails()))
              .append("</d>");
        }
      } else if (node instanceof ExceptionReportNode) {
        ExceptionReportNode exceptionNode = (ExceptionReportNode) node;
        text.append("<e>")
            .append(serializeExceptionInfo(exceptionNode.getException()))
            .append("</e>");
      } else if (node instanceof BranchReportNode) {
        BranchReportNode branch = (BranchReportNode) node;
        text.append("<c>");
        if (branch.getChildNodes() != null) {
          for (ReportNode rn : branch.getChildNodes()) {
            text.append(render(rn));
          }
        }
        text.append("</c>");
      }

      text.append("</r>");
      return text.toString();
    } else {
      throw new IllegalArgumentException("Main report node should not be null");
    }
  }
  /**
   * Updates the status of all report nodes
   *
   * @param node
   */
  public int updateReport(ReportNode node, int step, IdGenerator idGenerator) {
    node.setId(idGenerator.makeUniqueId());

    Map<String, Object> metaData = new HashMap<String, Object>();
    new HashMap<String, Object>();
    metaData.put("collapsed", true);

    metaData.put("nodeClassName", node.getClass().getSimpleName());
    if (node.hasLevel(ReportNode.ERROR)) {
      metaData.put("hasError", true);
    } else if (node.hasLevel(ReportNode.WARN)) {
      metaData.put("hasWarn", true);
    }

    metaData.put("isSimple", false);
    metaData.put("hasChildren", false);

    if (node instanceof BranchReportNode) {
      metaData.put("type", "branch");
      BranchReportNode branchNode = (BranchReportNode) node;
      if (branchNode.getChildNodes() != null && branchNode.getChildNodes().size() > 0) {
        metaData.put("hasChildren", true);
        for (ReportNode childNode : branchNode.getChildNodes()) {
          step = updateReport(childNode, step, idGenerator);
        }
      } else {
        metaData.put("isSimple", true);
      }
    } else if (node instanceof TextReportNode) {
      step++;
      metaData.put("step", step);
      metaData.put("type", "text");
      TextReportNode textNode = (TextReportNode) node;
      if (textNode.getDetails() == null || textNode.getDetails().trim().isEmpty()) {
        metaData.put("isSimple", true);
      }
    } else if (node instanceof ExceptionReportNode) {
      metaData.put("type", "exception");
    }

    node.setMetaData(metaData);
    if (node.getParentBranch() != null) {
      metaData.put("parentId", node.getParentBranch().getId());
    }
    return step;
  }