gt:                         Map<String, String> propMap)
context: /*
  // This software is subject to the terms of the Eclipse Public License v1.0
  // Agreement, available at the following URL:
  // http://www.eclipse.org/legal/epl-v10.html.
  // You must accept the terms of that agreement to use this software.
  //
  // Copyright (C) 2003-2005 Julian Hyde
  // Copyright (C) 2005-2016 Pentaho
  // All Rights Reserved.
  */
  package mondrian.xmla;
  
  import mondrian.olap.MondrianException;
  import mondrian.olap.Util;
  import mondrian.util.XmlParserFactoryProducer;
  import mondrian.xmla.impl.DefaultXmlaResponse;
  
  import org.olap4j.OlapConnection;
  import org.olap4j.OlapException;
  
  import org.w3c.dom.*;
  import org.xml.sax.InputSource;
  
  import java.io.*;
  import java.nio.charset.Charset;
  import java.sql.SQLException;
  import java.util.*;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.regex.Pattern;
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  import javax.xml.transform.Transformer;
  import javax.xml.transform.TransformerFactory;
  import javax.xml.transform.dom.DOMSource;
  import javax.xml.transform.stream.StreamResult;
  
  import static org.olap4j.metadata.XmlaConstants.Format;
  import static org.olap4j.metadata.XmlaConstants.Method;
  
  /**
   * Utility methods for XML/A implementation.
   *
   * @author Gang Chen
   */
  public class XmlaUtil implements XmlaConstants {
  
      /**
       * Invalid characters for XML element name.
       *
       * <p>XML element name:
       *
       * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF]
       *        | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
       * S ::= (#x20 | #x9 | #xD | #xA)+
       * NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar
       *        | Extender
       * Name ::= (Letter | '_' | ':') (NameChar)*
       * Names ::= Name (#x20 Name)*
       * Nmtoken ::= (NameChar)+
       * Nmtokens ::= Nmtoken (#x20 Nmtoken)*
       *
       */
      private static final String[] CHAR_TABLE = new String[256];
      private static final Pattern LOWERCASE_PATTERN =
          Pattern.compile(".*[a-z].*");
  
      static {
          initCharTable(" \t\r\n(){}[]+/*%!,?");
      }
  
      private static void initCharTable(String charStr) {
          char[] chars = charStr.toCharArray();
          for (char c : chars) {
              CHAR_TABLE[c] = encodeChar(c);
          }
      }
  
      private static String encodeChar(char c) {
          StringBuilder buf = new StringBuilder();
          buf.append("_x");
          String str = Integer.toHexString(c);
          for (int i = 4 - str.length(); i > 0; i--) {
              buf.append("0");
          }
          return buf.append(str).append("_").toString();
      }
  
      /**
       * Encodes an XML element name.
       *
       * <p>This function is mainly for encode element names in result of Drill
       * Through execute, because its element names come from database, we cannot
       * make sure they are valid XML contents.
       *
       * <p>Quoth the <a href="http://xmla.org">XML/A specification</a>, version
       * 1.1:
       * <blockquote>
       * XML does not allow certain characters as element and attribute names.
       * XML for Analysis supports encoding as defined by SQL Server 2000 to
       * address this XML constraint. For column names that contain invalid XML
       * name characters (according to the XML 1.0 specification), the nonvalid
       * Unicode characters are encoded using the corresponding hexadecimal
       * values. These are escaped as _x<i>HHHH_</i> where <i>HHHH</i> stands for
       * the four-digit hexadecimal UCS-2 code for the character in
       * most-significant bit first order. For example, the name "Order Details"
       * is encoded as Order_<i>x0020</i>_Details, where the space character is
       * replaced by the corresponding hexadecimal code.
       * </blockquote>
       *
       * @param name Name of XML element
       * @return encoded name
       */
      private static String encodeElementName(String name) {
          StringBuilder buf = new StringBuilder();
          char[] nameChars = name.toCharArray();
          for (char ch : nameChars) {
              String encodedStr =
                  (ch >= CHAR_TABLE.length ? null : CHAR_TABLE[ch]);
              if (encodedStr == null) {
                  buf.append(ch);
              } else {
                  buf.append(encodedStr);
              }
          }
          return buf.toString();
      }
  
  
      public static void element2Text(Element elem, final StringWriter writer)
          throws XmlaException
      {
          try {
              TransformerFactory factory = TransformerFactory.newInstance();
              Transformer transformer = factory.newTransformer();
              transformer.transform(
                  new DOMSource(elem),
                  new StreamResult(writer));
          } catch (Exception e) {
              throw new XmlaException(
                  CLIENT_FAULT_FC,
                  USM_DOM_PARSE_CODE,
                  USM_DOM_PARSE_FAULT_FS,
                  e);
          }
      }
  
      public static Element text2Element(String text)
          throws XmlaException
      {
          return _2Element(new InputSource(new StringReader(text)));
      }
  
      public static Element stream2Element(InputStream stream)
          throws XmlaException
      {
          return _2Element(new InputSource(stream));
      }
  
      private static Element _2Element(InputSource source)
          throws XmlaException
      {
          try {
              DocumentBuilderFactory factory =
                  XmlParserFactoryProducer.createSecureDocBuilderFactory();
              factory.setIgnoringElementContentWhitespace(true);
              factory.setIgnoringComments(true);
              factory.setNamespaceAware(true);
  
              DocumentBuilder builder = factory.newDocumentBuilder();
              Document doc = builder.parse(source);
              return doc.getDocumentElement();
          } catch (Exception e) {
              throw new XmlaException(
                  CLIENT_FAULT_FC,
                  USM_DOM_PARSE_CODE,
                  USM_DOM_PARSE_FAULT_FS,
                  e);
          }
      }
  
      public static Element[] filterChildElements(
          Element parent,
          String ns,
          String lname)
      {
  /*
  way too noisy
          if (LOGGER.isDebugEnabled()) {
              StringBuilder buf = new StringBuilder(100);
              buf.append("XmlaUtil.filterChildElements: ");
              buf.append(" ns=\"");
              buf.append(ns);
              buf.append("\", lname=\"");
              buf.append(lname);
              buf.append("\"");
              LOGGER.debug(buf.toString());
          }
  */
  
          List<Element> elems = new ArrayList<Element>();
          NodeList nlst = parent.getChildNodes();
          for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) {
              Node n = nlst.item(i);
              if (n instanceof Element) {
                  Element e = (Element) n;
                  if ((ns == null || ns.equals(e.getNamespaceURI()))
                      && (lname == null || lname.equals(e.getLocalName())))
                  {
                      elems.add(e);
                  }
              }
          }
          return elems.toArray(new Element[elems.size()]);
      }
  
      public static String textInElement(Element elem) {
          StringBuilder buf = new StringBuilder(100);
          elem.normalize();
          NodeList nlst = elem.getChildNodes();
          for (int i = 0, nlen = nlst.getLength(); i < nlen ; i++) {
              Node n = nlst.item(i);
              if (n instanceof Text) {
                  final String data = ((Text) n).getData();
                  buf.append(data);
              }
          }
          return buf.toString();
      }
  
      /**
       * Finds root MondrianException in exception chain if exists,
       * otherwise the input throwable.
       *
       * @param throwable Exception
       * @return Root exception
       */
      public static Throwable rootThrowable(Throwable throwable) {
          Throwable rootThrowable = throwable.getCause();
          if (rootThrowable != null
              && rootThrowable instanceof MondrianException)
          {
              return rootThrowable(rootThrowable);
          }
          return throwable;
      }
  
      /**
       * Corrects for the differences between numeric strings arising because
       * JDBC drivers use different representations for numbers
       * ({@link Double} vs. {@link java.math.BigDecimal}) and
       * these have different toString() behavior.
       *
       * <p>If it contains a decimal point, then
       * strip off trailing '0's. After stripping off
       * the '0's, if there is nothing right of the
       * decimal point, then strip off decimal point.
       *
       * @param numericStr Numeric string
       * @return Normalized string
       */
      public static String normalizeNumericString(String numericStr) {
          int index = numericStr.indexOf('.');
          if (index > 0) {
              // If it uses exponential notation, 1.0E4, then it could
              // have a trailing '0' that should not be stripped of,
              // e.g., 1.0E10. This would be rather bad.
              if (numericStr.indexOf('e') != -1) {
                  return numericStr;
              } else if (numericStr.indexOf('E') != -1) {
                  return numericStr;
              }
  
              boolean found = false;
              int p = numericStr.length();
              char c = numericStr.charAt(p - 1);
              while (c == '0') {
                  found = true;
                  p--;
                  c = numericStr.charAt(p - 1);
              }
              if (c == '.') {
                  p--;
              }
              if (found) {
                  return numericStr.substring(0, p);
              }
          }
          return numericStr;
      }
  
      /**
       * Returns a set of column headings and rows for a given metadata request.
       *
       * <p/>Leverages mondrian's implementation of the XML/A specification, and
       * is exposed here for use by mondrian's olap4j driver.
       *
       * @param connection Connection
       * @param methodName Metadata method name per XMLA (e.g. "MDSCHEMA_CUBES")
       * @param restrictionMap Restrictions
       * @return Set of rows and column headings
       */
      public static MetadataRowset getMetadataRowset(
          final OlapConnection connection,
          String methodName,
          final Map<String, Object> restrictionMap)
          throws OlapException
      {
          RowsetDefinition rowsetDefinition =
              RowsetDefinition.valueOf(methodName);
  
          final XmlaHandler.ConnectionFactory connectionFactory =
              new XmlaHandler.ConnectionFactory() {
                  public OlapConnection getConnection(
                      String catalog, String schema, String roleName,
                      Properties props)
                      throws SQLException
                  {
                      return connection;
                  }
  
                  public Map<String, Object>
                  getPreConfiguredDiscoverDatasourcesResponse()
                  {
                      // This method should not be used by the olap4j xmla
                      // servlet. For the mondrian xmla servlet we don't provide
                      // the "pre configured discover datasources" feature.
                      return null;
                  }
              };
          final XmlaRequest request = new XmlaRequest() {
              public Method getMethod() {
                  return Method.DISCOVER;
              }
  
              public Map<String, String> getProperties() {
                  return Collections.emptyMap();
              }
  
              public Map<String, Object> getRestrictions() {
                  return restrictionMap;
              }
  
              public String getStatement() {
                  return null;
              }
  
              public String getRoleName() {
                  return null;
              }
  
              public String getRequestType() {
                  throw new UnsupportedOperationException();
              }
  
              public boolean isDrillThrough() {
                  throw new UnsupportedOperationException();
              }
  
              public Format getFormat() {
                  throw new UnsupportedOperationException();
              }
  
              public String getUsername() {
                  return null;
              }
  
              public String getPassword() {
                  return null;
              }
  
              public String getSessionId() {
                  return null;
              }
          };
          final Rowset rowset =
              rowsetDefinition.getRowset(
                  request,
                  new XmlaHandler(
                      connectionFactory,
                      "xmla")
                  {
                      @Override
                      public OlapConnection getConnection(
                          XmlaRequest request,
  