/// <summary>
        /// Gets a string representation of the custom criteria node. If it has
        /// children, each child will be appended to the string recursively.
        /// </summary>
        /// <param name="root">The root custom criteria node.</param>
        /// <param name="level">The level of the custom criteria tree.</param>
        /// <returns>
        /// A string representation of the custom criteria node and its
        /// children
        /// </returns>
        private static string getCustomCriteriaSetString(CustomCriteriaNode root, int level)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(new String('\t', level));

            if (root is CustomCriteria)
            {
                CustomCriteria customCriteria = (CustomCriteria)root;
                StringBuilder  ids            = new StringBuilder();
                for (int j = 0; j < customCriteria.valueIds.Length; j++)
                {
                    ids.Append(customCriteria.valueIds[j] + ", ");
                }

                sb.AppendFormat("Custom criteria: operator: [{0}] key: [{1}] values: [{2}]\n",
                                customCriteria.@operator, customCriteria.keyId, ids.ToString().TrimEnd(',', ' '));
                return(sb.ToString());
            }
            else if (root is CustomCriteriaSet)
            {
                CustomCriteriaSet customCriteriaSet = (CustomCriteriaSet)root;
                sb.AppendFormat("Custom criteria set: operator: [{0}] children: \n",
                                customCriteriaSet.logicalOperator);
                foreach (CustomCriteriaNode node in customCriteriaSet.children)
                {
                    sb.Append(getCustomCriteriaSetString(node, level + 1));
                }
                return(sb.Append("\n").ToString());
            }
            else
            {
                throw new Exception("Unexpected node: " + root.GetType().Name);
            }
        }
    /// <summary>
    /// Gets a string representation of the custom criteria node. If it has
    /// children, each child will be appended to the string recursively.
    /// </summary>
    /// <param name="root">The root custom criteria node.</param>
    /// <param name="level">The level of the custom criteria tree.</param>
    /// <returns>
    /// A string representation of the custom criteria node and its
    /// children
    /// </returns>
    private static string getCustomCriteriaSetString(CustomCriteriaNode root, int level) {
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < level; i++) {
        sb.Append("\t");
      }

      if (root is CustomCriteria) {
        CustomCriteria customCriteria = (CustomCriteria) root;
        StringBuilder ids = new StringBuilder();
        for (int j = 0; j < customCriteria.valueIds.Length; j++) {
          ids.Append(customCriteria.valueIds[j] + ", ");
        }

        sb.AppendFormat("Custom criteria: operator: [{0}] key: [{1}] values: [{2}]\n",
            customCriteria.@operator, customCriteria.keyId, ids.ToString().TrimEnd(',', ' '));
        return sb.ToString();
      } else if (root is CustomCriteriaSet) {
        CustomCriteriaSet customCriteriaSet = (CustomCriteriaSet) root;
        sb.AppendFormat("Custom criteria set: operator: [{0}] children: \n",
            customCriteriaSet.logicalOperator);
        foreach (CustomCriteriaNode node in customCriteriaSet.children) {
          sb.Append(getCustomCriteriaSetString(node, level + 1));
        }
        return sb.Append("\n").ToString();
      } else {
        throw new Exception("Unexpected node: " + root.GetType().Name);
      }
    }