Package mondrian.xmla

Source Code of mondrian.xmla.XmlaHandler$MDDataSet_Tabular

/*
// 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.
// Copyright (C) 2003-2010 Julian Hyde
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
*/
package mondrian.xmla;

import mondrian.calc.ResultStyle;
import mondrian.olap.*;
import mondrian.olap.Connection;
import mondrian.olap.DriverManager;
import mondrian.rolap.*;
import mondrian.rolap.agg.CellRequest;
import mondrian.spi.CatalogLocator;
import mondrian.spi.Dialect;
import mondrian.xmla.impl.DefaultSaxWriter;
import static mondrian.xmla.XmlaConstants.*;

import static org.olap4j.metadata.XmlaConstants.*;

import org.apache.log4j.Logger;
import org.xml.sax.SAXException;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
import java.util.*;
import java.io.*;

/**
* An <code>XmlaHandler</code> responds to XML for Analysis (XML/A) requests.
*
* @author jhyde, Gang Chen
* @version $Id: //open/mondrian-release/3.2/src/main/mondrian/xmla/XmlaHandler.java#11 $
* @since 27 April, 2003
*/
public class XmlaHandler {
    private static final Logger LOGGER = Logger.getLogger(XmlaHandler.class);

    private final Map<String, DataSourcesConfig.DataSource> dataSourcesMap;
    private final CatalogLocator catalogLocator;
    private final String prefix;

    private enum SetType {
        ROW_SET,
        MD_DATA_SET
    }

    private static final String EMPTY_ROW_SET_XML_SCHEMA =
        computeEmptyXsd(SetType.ROW_SET);

    private static final String MD_DATA_SET_XML_SCHEMA =
        computeXsd(SetType.MD_DATA_SET);

    private static final String EMPTY_MD_DATA_SET_XML_SCHEMA =
        computeEmptyXsd(SetType.MD_DATA_SET);

    private static final String NS_XML_SQL =
        "urn:schemas-microsoft-com:xml-sql";

    //
    // Some xml schema data types.
    //
    public static final String XSD_BOOLEAN = "xsd:boolean";
    public static final String XSD_STRING = "xsd:string";
    public static final String XSD_UNSIGNED_INT = "xsd:unsignedInt";

    public static final String XSD_BYTE = "xsd:byte";
    public static final byte XSD_BYTE_MAX_INCLUSIVE = 127;
    public static final byte XSD_BYTE_MIN_INCLUSIVE = -128;

    public static final String XSD_SHORT = "xsd:short";
    public static final short XSD_SHORT_MAX_INCLUSIVE = 32767;
    public static final short XSD_SHORT_MIN_INCLUSIVE = -32768;

    public static final String XSD_INT = "xsd:int";
    public static final int XSD_INT_MAX_INCLUSIVE = 2147483647;
    public static final int XSD_INT_MIN_INCLUSIVE = -2147483648;

    public static final String XSD_LONG = "xsd:long";
    public static final long XSD_LONG_MAX_INCLUSIVE = 9223372036854775807L;
    public static final long XSD_LONG_MIN_INCLUSIVE = -9223372036854775808L;

    // xsd:double: IEEE 64-bit floating-point
    public static final String XSD_DOUBLE = "xsd:double";

    // xsd:decimal: Decimal numbers (BigDecimal)
    public static final String XSD_DECIMAL = "xsd:decimal";

    // xsd:integer: Signed integers of arbitrary length (BigInteger)
    public static final String XSD_INTEGER = "xsd:integer";

    public static boolean isValidXsdInt(long l) {
        return (l <= XSD_INT_MAX_INCLUSIVE) && (l >= XSD_INT_MIN_INCLUSIVE);
    }

    /**
     * Takes a DataType String (null, Integer, Numeric or non-null)
     * and Value Object (Integer, Double, String, other) and
     * canonicalizes them to XSD data type and corresponding object.
     * <p>
     * If the input DataType is Integer, then it attempts to return
     * an XSD_INT with value java.lang.Integer (and failing that an
     * XSD_LONG (java.lang.Long) or XSD_INTEGER (java.math.BigInteger)).
     * Worst case is the value loses precision with any integral
     * representation and must be returned as a decimal type (Double
     * or java.math.BigDecimal).
     * <p>
     * If the input DataType is Decimal, then it attempts to return
     * an XSD_DOUBLE with value java.lang.Double (and failing that an
     * XSD_DECIMAL (java.math.BigDecimal)).
     */
    static class ValueInfo {

        /**
         * Returns XSD_INT, XSD_DOUBLE, XSD_STRING or null.
         *
         * @param dataType null, Integer, Numeric or non-null.
         * @return Returns the suggested XSD type for a given datatype
         */
        static String getValueTypeHint(final String dataType) {
            if (dataType != null) {
                return (dataType.equals("Integer"))
                    ? XSD_INT
                    : ((dataType.equals("Numeric"))
                        ? XSD_DOUBLE
                        : XSD_STRING);
            } else {
                return null;
            }
        }

        String valueType;
        Object value;
        boolean isDecimal;

        ValueInfo(final String dataType, final Object inputValue) {
            final String valueTypeHint = getValueTypeHint(dataType);

            // This is a hint: should it be a string, integer or decimal type.
            // In the following, if the hint is integer, then there is
            // an attempt that the value types
            // be XSD_INT, XST_LONG, or XSD_INTEGER (but they could turn
            // out to be XSD_DOUBLE or XSD_DECIMAL if precision is loss
            // with the integral formats). It the hint is a decimal type
            // (double, float, decimal), then a XSD_DOUBLE or XSD_DECIMAL
            // is returned.
            if (valueTypeHint != null) {
                // The value type is a hint. If the value can be
                // converted to the data type without precision loss, ok;
                // otherwise value data type must be adjusted.

                if (valueTypeHint.equals(XSD_STRING)) {
                    // For String types, nothing to do.
                    this.valueType = valueTypeHint;
                    this.value = inputValue;
                    this.isDecimal = false;

                } else if (valueTypeHint.equals(XSD_INT)) {
                    // If valueTypeHint is XSD_INT, then see if value can be
                    // converted to (first choice) integer, (second choice),
                    // long and (last choice) BigInteger - otherwise must
                    // use double/decimal.

                    // Most of the time value ought to be an Integer so
                    // try it first
                    if (inputValue instanceof Integer) {
                        // For integer, its already the right type
                        this.valueType = valueTypeHint;
                        this.value = inputValue;
                        this.isDecimal = false;

                    } else if (inputValue instanceof Byte) {
                        this.valueType = valueTypeHint;
                        this.value = inputValue;
                        this.isDecimal = false;

                    } else if (inputValue instanceof Short) {
                        this.valueType = valueTypeHint;
                        this.value = inputValue;
                        this.isDecimal = false;

                    } else if (inputValue instanceof Long) {
                        // See if it can be an integer or long
                        long lval = (Long) inputValue;
                        setValueAndType(lval);

                    } else if (inputValue instanceof BigInteger) {
                        BigInteger bi = (BigInteger) inputValue;
                        // See if it can be an integer or long
                        long lval = bi.longValue();
                        if (bi.equals(BigInteger.valueOf(lval))) {
                            // It can be converted from BigInteger to long
                            // without loss of precision.
                            setValueAndType(lval);
                        } else {
                            // It can not be converted to a long.
                            this.valueType = XSD_INTEGER;
                            this.value = inputValue;
                            this.isDecimal = false;
                        }

                    } else if (inputValue instanceof Float) {
                        Float f = (Float) inputValue;
                        // See if it can be an integer or long
                        long lval = f.longValue();
                        if (f.equals(new Float(lval))) {
                            // It can be converted from double to long
                            // without loss of precision.
                            setValueAndType(lval);

                        } else {
                            // It can not be converted to a long.
                            this.valueType = XSD_DOUBLE;
                            this.value = inputValue;
                            this.isDecimal = true;
                        }

                    } else if (inputValue instanceof Double) {
                        Double d = (Double) inputValue;
                        // See if it can be an integer or long
                        long lval = d.longValue();
                        if (d.equals(new Double(lval))) {
                            // It can be converted from double to long
                            // without loss of precision.
                            setValueAndType(lval);

                        } else {
                            // It can not be converted to a long.
                            this.valueType = XSD_DOUBLE;
                            this.value = inputValue;
                            this.isDecimal = true;
                        }

                    } else if (inputValue instanceof BigDecimal) {
                        // See if it can be an integer or long
                        BigDecimal bd = (BigDecimal) inputValue;
                        try {
                            // Can it be converted to a long
                            // Throws ArithmeticException on conversion failure.
                            // The following line is only available in
                            // Java5 and above:
                            //long lval = bd.longValueExact();
                            long lval = bd.longValue();

                            setValueAndType(lval);
                        } catch (ArithmeticException ex) {
                            // No, it can not be converted to long

                            try {
                                // Can it be an integer
                                BigInteger bi = bd.toBigIntegerExact();
                                this.valueType = XSD_INTEGER;
                                this.value = bi;
                                this.isDecimal = false;
                            } catch (ArithmeticException ex1) {
                                // OK, its a decimal
                                this.valueType = XSD_DECIMAL;
                                this.value = inputValue;
                                this.isDecimal = true;
                            }
                        }

                    } else if (inputValue instanceof Number) {
                        // Don't know what Number type we have here.
                        // Note: this could result in precision loss.
                        this.value = ((Number) inputValue).longValue();
                        this.valueType = valueTypeHint;
                        this.isDecimal = false;

                    } else {
                        // Who knows what we are dealing with,
                        // hope for the best?!?
                        this.valueType = valueTypeHint;
                        this.value = inputValue;
                        this.isDecimal = false;
                    }

                } else if (valueTypeHint.equals(XSD_DOUBLE)) {
                    // The desired type is double.

                    // Most of the time value ought to be an Double so
                    // try it first
                    if (inputValue instanceof Double) {
                        // For Double, its already the right type
                        this.valueType = valueTypeHint;
                        this.value = inputValue;
                        this.isDecimal = true;

                    } else if (inputValue instanceof Byte
                        || inputValue instanceof Short
                        || inputValue instanceof Integer
                        || inputValue instanceof Long)
                    {
                        // Convert from byte/short/integer/long to double
                        this.value = ((Number) inputValue).doubleValue();
                        this.valueType = valueTypeHint;
                        this.isDecimal = true;

                    } else if (inputValue instanceof Float) {
                        this.value = inputValue;
                        this.valueType = valueTypeHint;
                        this.isDecimal = true;

                    } else if (inputValue instanceof BigDecimal) {
                        BigDecimal bd = (BigDecimal) inputValue;
                        double dval = bd.doubleValue();
                        // make with same scale as Double
                        try {
                            BigDecimal bd2 =
                                Util.makeBigDecimalFromDouble(dval);
                            // Can it be a double
                            // Must use compareTo - see BigDecimal.equals
                            if (bd.compareTo(bd2) == 0) {
                                this.valueType = XSD_DOUBLE;
                                this.value = dval;
                            } else {
                                this.valueType = XSD_DECIMAL;
                                this.value = inputValue;
                            }
                        } catch (NumberFormatException ex) {
                            this.valueType = XSD_DECIMAL;
                            this.value = inputValue;
                        }
                        this.isDecimal = true;

                    } else if (inputValue instanceof BigInteger) {
                        // What should be done here? Convert ot BigDecimal
                        // and see if it can be a double or not?
                        // See if there is loss of precision in the convertion?
                        // Don't know. For now, just keep it a integral
                        // value.
                        BigInteger bi = (BigInteger) inputValue;
                        // See if it can be an integer or long
                        long lval = bi.longValue();
                        if (bi.equals(BigInteger.valueOf(lval))) {
                            // It can be converted from BigInteger to long
                            // without loss of precision.
                            setValueAndType(lval);
                        } else {
                            // It can not be converted to a long.
                            this.valueType = XSD_INTEGER;
                            this.value = inputValue;
                            this.isDecimal = true;
                        }

                    } else if (inputValue instanceof Number) {
                        // Don't know what Number type we have here.
                        // Note: this could result in precision loss.
                        this.value = ((Number) inputValue).doubleValue();
                        this.valueType = valueTypeHint;
                        this.isDecimal = true;

                    } else {
                        // Who knows what we are dealing with,
                        // hope for the best?!?
                        this.valueType = valueTypeHint;
                        this.value = inputValue;
                        this.isDecimal = true;
                    }
                }
            } else {
                // There is no valueType "hint", so just get it from the value.
                if (inputValue instanceof String) {
                    this.valueType = XSD_STRING;
                    this.value = inputValue;
                    this.isDecimal = false;

                } else if (inputValue instanceof Integer) {
                    this.valueType = XSD_INT;
                    this.value = inputValue;
                    this.isDecimal = false;

                } else if (inputValue instanceof Byte) {
                    Byte b = (Byte) inputValue;
                    this.valueType = XSD_INT;
                    this.value = b.intValue();
                    this.isDecimal = false;

                } else if (inputValue instanceof Short) {
                    Short s = (Short) inputValue;
                    this.valueType = XSD_INT;
                    this.value = s.intValue();
                    this.isDecimal = false;

                } else if (inputValue instanceof Long) {
                    // See if it can be an integer or long
                    setValueAndType((Long) inputValue);

                } else if (inputValue instanceof BigInteger) {
                    BigInteger bi = (BigInteger) inputValue;
                    // See if it can be an integer or long
                    long lval = bi.longValue();
                    if (bi.equals(BigInteger.valueOf(lval))) {
                        // It can be converted from BigInteger to long
                        // without loss of precision.
                        setValueAndType(lval);
                    } else {
                        // It can not be converted to a long.
                        this.valueType = XSD_INTEGER;
                        this.value = inputValue;
                        this.isDecimal = false;
                    }

                } else if (inputValue instanceof Float) {
                    this.valueType = XSD_DOUBLE;
                    this.value = inputValue;
                    this.isDecimal = true;

                } else if (inputValue instanceof Double) {
                    this.valueType = XSD_DOUBLE;
                    this.value = inputValue;
                    this.isDecimal = true;

                } else if (inputValue instanceof BigDecimal) {
                    // See if it can be a double
                    BigDecimal bd = (BigDecimal) inputValue;
                    double dval = bd.doubleValue();
                    // make with same scale as Double
                    try {
                        BigDecimal bd2 =
                                Util.makeBigDecimalFromDouble(dval);
                        // Can it be a double
                        // Must use compareTo - see BigDecimal.equals
                        if (bd.compareTo(bd2) == 0) {
                            this.valueType = XSD_DOUBLE;
                            this.value = dval;
                        } else {
                            this.valueType = XSD_DECIMAL;
                            this.value = inputValue;
                        }
                    } catch (NumberFormatException ex) {
                        this.valueType = XSD_DECIMAL;
                        this.value = inputValue;
                    }
                    this.isDecimal = true;

                } else if (inputValue instanceof Number) {
                    // Don't know what Number type we have here.
                    // Note: this could result in precision loss.
                    this.value = ((Number) inputValue).longValue();
                    this.valueType = XSD_LONG;
                    this.isDecimal = false;

                } else {
                    // Who knows what we are dealing with,
                    // hope for the best?!?
                    this.valueType = XSD_STRING;
                    this.value = inputValue;
                    this.isDecimal = false;
                }
            }
        }
        private void setValueAndType(long lval) {
            if (! isValidXsdInt(lval)) {
                // No, it can not be a integer, must be a long
                this.valueType = XSD_LONG;
                this.value = lval;
            } else {
                // Its an integer.
                this.valueType = XSD_INT;
                this.value = (int) lval;
            }
            this.isDecimal = false;
        }
    }



    private static String computeXsd(SetType setType) {
        final StringWriter sw = new StringWriter();
        SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3);
        writeDatasetXmlSchema(writer, setType);
        writer.flush();
        return sw.toString();
    }

    private static String computeEmptyXsd(SetType setType) {
        final StringWriter sw = new StringWriter();
        SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3);
        writeEmptyDatasetXmlSchema(writer, setType);
        writer.flush();
        return sw.toString();
    }

    private static interface QueryResult {
        public void unparse(SaxWriter res) throws SAXException;
    }

    /**
     * Creates an <code>XmlaHandler</code>.
     *
     * @param dataSources Data sources
     * @param catalogLocator Catalog locator
     * @param prefix XML Namespace. Typical value is "xmla", but a value of
     *   "cxmla" works around an Internet Explorer 7 bug
     */
    public XmlaHandler(
        DataSourcesConfig.DataSources dataSources,
        CatalogLocator catalogLocator,
        String prefix)
    {
        this.catalogLocator = catalogLocator;
        assert prefix != null;
        this.prefix = prefix;
        Map<String, DataSourcesConfig.DataSource> map =
            new HashMap<String, DataSourcesConfig.DataSource>();
        if (dataSources != null) {
            for (DataSourcesConfig.DataSource ds : dataSources.dataSources) {
                if (map.containsKey(ds.getDataSourceName())) {
                    // This is not an XmlaException
                    throw Util.newError(
                        "duplicated data source name '"
                        + ds.getDataSourceName() + "'");
                }
                // Set parent pointers.
                for (DataSourcesConfig.Catalog catalog : ds.catalogs.catalogs) {
                    catalog.setDataSource(ds);
                }
                map.put(ds.getDataSourceName(), ds);
            }
        }
        dataSourcesMap = Collections.unmodifiableMap(map);
    }

    public Map<String, DataSourcesConfig.DataSource> getDataSourceEntries() {
        return dataSourcesMap;
    }

    /**
     * Processes a request.
     *
     * @param request  XML request, for example, "<SOAP-ENV:Envelope ...>".
     * @param response Destination for response
     * @throws XmlaException on error
     */
    public void process(XmlaRequest request, XmlaResponse response)
        throws XmlaException
    {
        Method method = request.getMethod();
        long start = System.currentTimeMillis();

        switch (method) {
        case DISCOVER:
            discover(request, response);
            break;
        case EXECUTE:
            execute(request, response);
            break;
        default:
            throw new XmlaException(
                CLIENT_FAULT_FC,
                HSB_BAD_METHOD_CODE,
                HSB_BAD_METHOD_FAULT_FS,
                new IllegalArgumentException(
                    "Unsupported XML/A method: " + method));
        }
        if (LOGGER.isDebugEnabled()) {
            long end = System.currentTimeMillis();
            LOGGER.debug("XmlaHandler.process: time = " + (end - start));
            LOGGER.debug("XmlaHandler.process: " + Util.printMemory());
        }
    }

    private void checkFormat(XmlaRequest request) throws XmlaException {
        // Check response's rowset format in request
        final Map<String, String> properties = request.getProperties();
        if (request.isDrillThrough()) {
            Format format = getFormat(request, null);
            if (format != Format.Tabular) {
                throw new XmlaException(
                    CLIENT_FAULT_FC,
                    HSB_DRILL_THROUGH_FORMAT_CODE,
                    HSB_DRILL_THROUGH_FORMAT_FAULT_FS,
                    new UnsupportedOperationException(
                        "<Format>: only 'Tabular' allowed when drilling "
                        + "through"));
            }
        } else {
            final String formatName =
                properties.get(PropertyDefinition.Format.name());
            if (formatName != null) {
                Format format = getFormat(request, null);
                if (format != Format.Multidimensional
                    && format != Format.Tabular)
                {
                    throw new UnsupportedOperationException(
                        "<Format>: only 'Multidimensional', 'Tabular' "
                        + "currently supported");
                }
            }
            final String axisFormatName =
                properties.get(PropertyDefinition.AxisFormat.name());
            if (axisFormatName != null) {
                AxisFormat axisFormat = Util.lookup(
                    AxisFormat.class, axisFormatName, null);

                if (axisFormat != AxisFormat.TupleFormat) {
                    throw new UnsupportedOperationException(
                        "<AxisFormat>: only 'TupleFormat' currently supported");
                }
            }
        }
    }

    private void execute(
        XmlaRequest request,
        XmlaResponse response)
        throws XmlaException
    {
        final Map<String, String> properties = request.getProperties();

        // Default responseMimeType is SOAP.
        Enumeration.ResponseMimeType responseMimeType =
            getResponseMimeType(request);

        // Default value is SchemaData, or Data for JSON responses.
        final String contentName =
            properties.get(PropertyDefinition.Content.name());
        Content content = Util.lookup(
            Content.class,
            contentName,
            responseMimeType == Enumeration.ResponseMimeType.JSON
                ? Content.Data
                : Content.DEFAULT);

        // Handle execute
        QueryResult result;
        if (request.isDrillThrough()) {
            result = executeDrillThroughQuery(request);
        } else {
            result = executeQuery(request);
        }

        SaxWriter writer = response.getWriter();
        writer.startDocument();

        writer.startElement(
            prefix + ":ExecuteResponse",
            "xmlns:" + prefix, NS_XMLA);
        writer.startElement(prefix + ":return");
        boolean rowset =
            request.isDrillThrough()
            || Format.Tabular.name().equals(
                request.getProperties().get(
                    PropertyDefinition.Format.name()));
        writer.startElement(
            "root",
            "xmlns",
            result == null
            ? NS_XMLA_EMPTY
            : rowset
            ? NS_XMLA_ROWSET
            : NS_XMLA_MDDATASET,
            "xmlns:xsi", NS_XSI,
            "xmlns:xsd", NS_XSD,
            "xmlns:EX", NS_XMLA_EX);

        if ((content == Content.Schema)
            || (content == Content.SchemaData))
        {
            if (result != null) {
                if (result instanceof MDDataSet_Tabular) {
                    MDDataSet_Tabular tabResult = (MDDataSet_Tabular) result;
                    tabResult.metadata(writer);
                } else if (rowset) {
                    ((TabularRowSet) result).metadata(writer);
                } else {
                    writer.verbatim(MD_DATA_SET_XML_SCHEMA);
                }
            } else {
                if (rowset) {
                    writer.verbatim(EMPTY_ROW_SET_XML_SCHEMA);
                } else {
                    writer.verbatim(EMPTY_MD_DATA_SET_XML_SCHEMA);
                }
            }
        }

        try {
            switch (content) {
            case Data:
            case SchemaData:
            case DataOmitDefaultSlicer:
            case DataIncludeDefaultSlicer:
                if (result != null) {
                    result.unparse(writer);
                }
                break;
            }
        } catch (XmlaException xex) {
            throw xex;
        } catch (Throwable t) {
            throw new XmlaException(
                SERVER_FAULT_FC,
                HSB_EXECUTE_UNPARSE_CODE,
                HSB_EXECUTE_UNPARSE_FAULT_FS,
                t);
        } finally {
            writer.endElement(); // root
            writer.endElement(); // return
            writer.endElement(); // ExecuteResponse
        }

        writer.endDocument();
    }

    /**
     * Computes the XML Schema for a dataset.
     *
     * @param writer SAX writer
     * @param settype rowset or dataset?
     * @see RowsetDefinition#writeRowsetXmlSchema(SaxWriter)
     */
    static void writeDatasetXmlSchema(SaxWriter writer, SetType settype) {
        String setNsXmla =
            (settype == SetType.ROW_SET)
            ? NS_XMLA_ROWSET
            : NS_XMLA_MDDATASET;

        writer.startElement(
            "xsd:schema",
            "xmlns:xsd", NS_XSD,
            "targetNamespace", setNsXmla,
            "xmlns", setNsXmla,
            "xmlns:xsi", NS_XSI,
            "xmlns:sql", NS_XML_SQL,
            "elementFormDefault", "qualified");

        // MemberType

        writer.startElement(
            "xsd:complexType",
            "name", "MemberType");
        writer.startElement("xsd:sequence");
        writer.element(
            "xsd:element",
            "name", "UName",
            "type", XSD_STRING);
        writer.element(
            "xsd:element",
            "name", "Caption",
            "type", XSD_STRING);
        writer.element(
            "xsd:element",
            "name", "LName",
            "type", XSD_STRING);
        writer.element(
            "xsd:element",
            "name", "LNum",
            "type", XSD_UNSIGNED_INT);
        writer.element(
            "xsd:element",
            "name", "DisplayInfo",
            "type", XSD_UNSIGNED_INT);
        writer.startElement(
            "xsd:sequence",
            "maxOccurs", "unbounded",
            "minOccurs", 0);
        writer.element(
            "xsd:any",
            "processContents", "lax",
            "maxOccurs", "unbounded");
        writer.endElement(); // xsd:sequence
        writer.endElement(); // xsd:sequence
        writer.element(
            "xsd:attribute",
                "name", "Hierarchy",
                "type", XSD_STRING);
        writer.endElement(); // xsd:complexType name="MemberType"

        // PropType

        writer.startElement(
            "xsd:complexType",
            "name", "PropType");
        writer.element(
            "xsd:attribute",
            "name", "name",
            "type", XSD_STRING);
        writer.endElement(); // xsd:complexType name="PropType"

        // TupleType

        writer.startElement(
            "xsd:complexType",
            "name", "TupleType");
        writer.startElement(
            "xsd:sequence",
            "maxOccurs", "unbounded");
        writer.element(
            "xsd:element",
            "name", "Member",
            "type", "MemberType");
        writer.endElement(); // xsd:sequence
        writer.endElement(); // xsd:complexType name="TupleType"

        // MembersType

        writer.startElement(
            "xsd:complexType",
            "name", "MembersType");
        writer.startElement(
            "xsd:sequence",
            "maxOccurs", "unbounded");
        writer.element(
            "xsd:element",
            "name", "Member",
            "type", "MemberType");
        writer.endElement(); // xsd:sequence
        writer.element(
            "xsd:attribute",
            "name", "Hierarchy",
            "type", XSD_STRING);
        writer.endElement(); // xsd:complexType

        // TuplesType

        writer.startElement(
            "xsd:complexType",
            "name", "TuplesType");
        writer.startElement(
            "xsd:sequence",
            "maxOccurs", "unbounded");
        writer.element(
            "xsd:element",
            "name", "Tuple",
            "type", "TupleType");
        writer.endElement(); // xsd:sequence
        writer.endElement(); // xsd:complexType

        // CrossProductType

        writer.startElement(
            "xsd:complexType",
            "name", "CrossProductType");
        writer.startElement("xsd:sequence");
        writer.startElement(
            "xsd:choice",
            "minOccurs", 0,
            "maxOccurs", "unbounded");
        writer.element(
            "xsd:element",
            "name", "Members",
            "type", "MembersType");
        writer.element(
            "xsd:element",
            "name", "Tuples",
            "type", "TuplesType");
        writer.endElement(); // xsd:choice
        writer.endElement(); // xsd:sequence
        writer.element(
            "xsd:attribute",
            "name", "Size",
            "type", XSD_UNSIGNED_INT);
        writer.endElement(); // xsd:complexType

        // OlapInfo

        writer.startElement(
            "xsd:complexType",
            "name", "OlapInfo");
        writer.startElement("xsd:sequence");

        { // <CubeInfo>
            writer.startElement(
                "xsd:element",
                "name", "CubeInfo");
            writer.startElement("xsd:complexType");
            writer.startElement("xsd:sequence");

            { // <Cube>
                writer.startElement(
                    "xsd:element",
                    "name", "Cube",
                    "maxOccurs", "unbounded");
                writer.startElement("xsd:complexType");
                writer.startElement("xsd:sequence");

                writer.element(
                    "xsd:element",
                    "name", "CubeName",
                    "type", XSD_STRING);

                writer.endElement(); // xsd:sequence
                writer.endElement(); // xsd:complexType
                writer.endElement(); // xsd:element name=Cube
            }

            writer.endElement(); // xsd:sequence
            writer.endElement(); // xsd:complexType
            writer.endElement(); // xsd:element name=CubeInfo
        }

        { // <AxesInfo>
            writer.startElement(
                "xsd:element",
                "name", "AxesInfo");
            writer.startElement("xsd:complexType");
            writer.startElement("xsd:sequence");
            { // <AxisInfo>
                writer.startElement(
                    "xsd:element",
                    "name", "AxisInfo",
                    "maxOccurs", "unbounded");
                writer.startElement("xsd:complexType");
                writer.startElement("xsd:sequence");

                { // <HierarchyInfo>
                    writer.startElement(
                        "xsd:element",
                        "name", "HierarchyInfo",
                        "minOccurs", 0,
                        "maxOccurs", "unbounded");
                    writer.startElement("xsd:complexType");
                    writer.startElement("xsd:sequence");
                    writer.startElement(
                        "xsd:sequence",
                        "maxOccurs", "unbounded");
                    writer.element(
                        "xsd:element",
                        "name", "UName",
                        "type", "PropType");
                    writer.element(
                        "xsd:element",
                        "name", "Caption",
                        "type", "PropType");
                    writer.element(
                        "xsd:element",
                        "name", "LName",
                        "type", "PropType");
                    writer.element(
                        "xsd:element",
                        "name", "LNum",
                        "type", "PropType");
                    writer.element(
                        "xsd:element",
                        "name", "DisplayInfo",
                        "type", "PropType",
                        "minOccurs", 0,
                        "maxOccurs", "unbounded");
                    if (false) writer.element(
                        "xsd:element",
                        "name", "PARENT_MEMBER_NAME",
                        "type", "PropType",
                        "minOccurs", 0,
                        "maxOccurs", "unbounded");
                    writer.endElement(); // xsd:sequence

                    // This is the Depth element for JPivot??
                    writer.startElement("xsd:sequence");
                    writer.element(
                        "xsd:any",
                        "processContents", "lax",
                        "minOccurs", 0,
                        "maxOccurs", "unbounded");
                    writer.endElement(); // xsd:sequence

                    writer.endElement(); // xsd:sequence
                    writer.element(
                        "xsd:attribute",
                        "name", "name",
                        "type", XSD_STRING,
                        "use", "required");
                    writer.endElement(); // xsd:complexType
                    writer.endElement(); // xsd:element name=HierarchyInfo
                }
                writer.endElement(); // xsd:sequence
                writer.element(
                    "xsd:attribute",
                    "name", "name",
                    "type", XSD_STRING);
                writer.endElement(); // xsd:complexType
                writer.endElement(); // xsd:element name=AxisInfo
            }
            writer.endElement(); // xsd:sequence
            writer.endElement(); // xsd:complexType
            writer.endElement(); // xsd:element name=AxesInfo
        }

        // CellInfo

        { // <CellInfo>
            writer.startElement(
                "xsd:element",
                "name", "CellInfo");
            writer.startElement("xsd:complexType");
            writer.startElement("xsd:sequence");
            writer.startElement(
                "xsd:sequence",
                "minOccurs", 0,
                "maxOccurs", "unbounded");
            writer.startElement("xsd:choice");
            writer.element(
                "xsd:element",
                "name", "Value",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "FmtValue",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "BackColor",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "ForeColor",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "FontName",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "FontSize",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "FontFlags",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "FormatString",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "NonEmptyBehavior",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "SolveOrder",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "Updateable",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "Visible",
                "type", "PropType");
            writer.element(
                "xsd:element",
                "name", "Expression",
                "type", "PropType");
            writer.endElement(); // xsd:choice
            writer.endElement(); // xsd:sequence
            writer.startElement(
                "xsd:sequence",
                "maxOccurs", "unbounded",
                "minOccurs", 0);
            writer.element(
                "xsd:any",
                "processContents", "lax",
                "maxOccurs", "unbounded");
            writer.endElement(); // xsd:sequence
            writer.endElement(); // xsd:sequence
            writer.endElement(); // xsd:complexType
            writer.endElement(); // xsd:element name=CellInfo
        }

        writer.endElement(); // xsd:sequence
        writer.endElement(); // xsd:complexType

        // Axes

        writer.startElement(
            "xsd:complexType",
            "name", "Axes");
        writer.startElement(
            "xsd:sequence",
            "maxOccurs", "unbounded");
        { // <Axis>
            writer.startElement(
                "xsd:element",
                "name", "Axis");
            writer.startElement("xsd:complexType");
            writer.startElement(
                "xsd:choice",
                "minOccurs", 0,
                "maxOccurs", "unbounded");
            writer.element(
                "xsd:element",
                "name", "CrossProduct",
                "type", "CrossProductType");
            writer.element(
                "xsd:element",
                "name", "Tuples",
                "type", "TuplesType");
            writer.element(
                "xsd:element",
                "name", "Members",
                "type", "MembersType");
            writer.endElement(); // xsd:choice
            writer.element(
                "xsd:attribute",
                "name", "name",
                "type", XSD_STRING);
            writer.endElement(); // xsd:complexType
        }
        writer.endElement(); // xsd:element
        writer.endElement(); // xsd:sequence
        writer.endElement(); // xsd:complexType

        // CellData

        writer.startElement(
            "xsd:complexType",
            "name", "CellData");
        writer.startElement("xsd:sequence");
        { // <Cell>
            writer.startElement(
                "xsd:element",
                "name", "Cell",
                "minOccurs", 0,
                "maxOccurs", "unbounded");
            writer.startElement("xsd:complexType");
            writer.startElement(
                "xsd:sequence",
                "maxOccurs", "unbounded");
            writer.startElement("xsd:choice");
            writer.element(
                "xsd:element",
                "name", "Value");
            writer.element(
                "xsd:element",
                "name", "FmtValue",
                "type", XSD_STRING);
            writer.element(
                "xsd:element",
                "name", "BackColor",
                "type", XSD_UNSIGNED_INT);
            writer.element(
                "xsd:element",
                "name", "ForeColor",
                "type", XSD_UNSIGNED_INT);
            writer.element(
                "xsd:element",
                "name", "FontName",
                "type", XSD_STRING);
            writer.element(
                "xsd:element",
                "name", "FontSize",
                "type", "xsd:unsignedShort");
            writer.element(
                "xsd:element",
                "name", "FontFlags",
                "type", XSD_UNSIGNED_INT);
            writer.element(
                "xsd:element",
                "name", "FormatString",
                "type", XSD_STRING);
            writer.element(
                "xsd:element",
                "name", "NonEmptyBehavior",
                "type", "xsd:unsignedShort");
            writer.element(
                "xsd:element",
                "name", "SolveOrder",
                "type", XSD_UNSIGNED_INT);
            writer.element(
                "xsd:element",
                "name", "Updateable",
                "type", XSD_UNSIGNED_INT);
            writer.element(
                "xsd:element",
                "name", "Visible",
                "type", XSD_UNSIGNED_INT);
            writer.element(
                "xsd:element",
                "name", "Expression",
                "type", XSD_STRING);
            writer.endElement(); // xsd:choice
            writer.endElement(); // xsd:sequence
            writer.element(
                "xsd:attribute",
                "name", "CellOrdinal",
                "type", XSD_UNSIGNED_INT,
                "use", "required");
            writer.endElement(); // xsd:complexType
            writer.endElement(); // xsd:element name=Cell
        }
        writer.endElement(); // xsd:sequence
        writer.endElement(); // xsd:complexType

        { // <root>
            writer.startElement(
                "xsd:element",
                "name", "root");
            writer.startElement("xsd:complexType");
            writer.startElement(
                "xsd:sequence",
                "maxOccurs", "unbounded");
            writer.element(
                "xsd:element",
                "name", "OlapInfo",
                "type", "OlapInfo");
            writer.element(
                "xsd:element",
                "name", "Axes",
                "type", "Axes");
            writer.element(
                "xsd:element",
                "name", "CellData",
                "type", "CellData");
            writer.endElement(); // xsd:sequence
            writer.endElement(); // xsd:complexType
            writer.endElement(); // xsd:element name=root
        }

        writer.endElement(); // xsd:schema
    }

    static void writeEmptyDatasetXmlSchema(SaxWriter writer, SetType setType) {
        String setNsXmla = NS_XMLA_ROWSET;
        writer.startElement(
            "xsd:schema",
            "xmlns:xsd", NS_XSD,
            "targetNamespace", setNsXmla,
            "xmlns", setNsXmla,
            "xmlns:xsi", NS_XSI,
            "xmlns:sql", NS_XML_SQL,
            "elementFormDefault", "qualified");

        writer.element(
            "xsd:element",
            "name", "root");

        writer.endElement(); // xsd:schema
    }

    private QueryResult executeDrillThroughQuery(XmlaRequest request)
        throws XmlaException
    {
        checkFormat(request);

        DataSourcesConfig.DataSource ds = getDataSource(request);
        DataSourcesConfig.Catalog dsCatalog = getCatalog(request, ds, true);
        String roleName = request.getRoleName();
        Role role = request.getRole();

        final Connection connection = getConnection(dsCatalog, role, roleName);

        final String statement = request.getStatement();
        final QueryPart parseTree = connection.parseStatement(statement);
        final DrillThrough drillThrough = (DrillThrough) parseTree;
        final Query query = drillThrough.getQuery();
        query.setResultStyle(ResultStyle.LIST);
        final Result result = connection.execute(query);
        // cell [0, 0] in a 2-dimensional query, [0, 0, 0] in 3 dimensions, etc.
        final int[] coords = new int[result.getAxes().length];
        Cell dtCell = result.getCell(coords);

        if (!dtCell.canDrillThrough()) {
            throw new XmlaException(
                SERVER_FAULT_FC,
                HSB_DRILL_THROUGH_NOT_ALLOWED_CODE,
                HSB_DRILL_THROUGH_NOT_ALLOWED_FAULT_FS,
                Util.newError("Cannot do DrillThrough operation on the cell"));
        }

        try {
            final Map<String, String> properties = request.getProperties();
            String tabFields =
                properties.get(PropertyDefinition.TableFields.name());
            if (tabFields != null && tabFields.length() == 0) {
                tabFields = null;
            }
            final String advancedFlag =
                properties.get(PropertyDefinition.AdvancedFlag.name());
            final boolean advanced = Boolean.parseBoolean(advancedFlag);
            if (advanced) {
                return executeDrillThroughAdvanced(connection, result);
            } else {
                int count = -1;
                if (MondrianProperties.instance().EnableTotalCount
                    .booleanValue())
                {
                    count = dtCell.getDrillThroughCount();
                }
                SqlStatement stmt2 =
                    ((RolapCell) dtCell).drillThroughInternal(
                        drillThrough.getMaxRowCount(),
                        Math.max(drillThrough.getFirstRowOrdinal(), 1),
                        tabFields,
                        true,
                        LOGGER);
                return new TabularRowSet(stmt2, count);
            }
        } catch (XmlaException xex) {
            throw xex;
        } catch (SQLException sqle) {
            throw new XmlaException(
                SERVER_FAULT_FC,
                HSB_DRILL_THROUGH_SQL_CODE,
                HSB_DRILL_THROUGH_SQL_FAULT_FS,
                Util.newError(sqle, "Error in drill through"));
        } catch (RuntimeException e) {
            throw new XmlaException(
                SERVER_FAULT_FC,
                HSB_DRILL_THROUGH_SQL_CODE,
                HSB_DRILL_THROUGH_SQL_FAULT_FS,
                e);
        }
    }

    private QueryResult executeDrillThroughAdvanced(
        Connection connection,
        Result result)
        throws SQLException
    {
        java.sql.Connection sqlConn = null;
        Statement stmt = null;
        try {
            final Axis axis = result.getAxes()[0];
            final Position position = axis.getPositions().get(0);
            Member[] members = position.toArray(
                new Member[position.size()]);

            final CellRequest cellRequest =
                RolapAggregationManager.makeRequest(members);
            List<MondrianDef.Relation> relationList =
                new ArrayList<MondrianDef.Relation>();
            final RolapStar.Table factTable =
                cellRequest.getMeasure().getStar().getFactTable();
            MondrianDef.Relation relation = factTable.getRelation();
            relationList.add(relation);

            for (RolapStar.Table table : factTable.getChildren()) {
                relationList.add(table.getRelation());
            }
            List<String> truncatedTableList = new ArrayList<String>();
            sqlConn = connection.getDataSource().getConnection();
            stmt = sqlConn.createStatement();
            List<List<String>> fields = new ArrayList<List<String>>();

            Map<String, List<String>> tableFieldMap =
                new HashMap<String, List<String>>();
            for (MondrianDef.Relation relation1 : relationList) {
                final String tableName = relation1.toString();
                List<String> fieldNameList = new ArrayList<String>();
                Dialect dialect =
                    ((RolapSchema) connection.getSchema()).getDialect();
                // FIXME: Include schema name, if specified.
                // FIXME: Deal with relations that are not tables.
                final StringBuilder buf = new StringBuilder();
                buf.append("SELECT * FROM ");
                dialect.quoteIdentifier(buf, tableName);
                buf.append(" WHERE 1=2");
                String sql = buf.toString();
                ResultSet rs = stmt.executeQuery(sql);
                ResultSetMetaData rsMeta = rs.getMetaData();
                for (int j = 1; j <= rsMeta.getColumnCount(); j++) {
                    // FIXME: In some JDBC drivers,
                    // ResultSetMetaData.getColumnName(int) does strange
                    // things with aliased columns. See MONDRIAN-654
                    // http://jira.pentaho.com/browse/MONDRIAN-654 for
                    // details. Therefore, we don't want to use that
                    // method. It seems harmless here, but I'd still
                    // like to phase out use of getColumnName. After
                    // PhysTable is introduced (coming in mondrian-4.0)
                    // we should be able to just use its column list.
                    String colName = rsMeta.getColumnName(j);
                    boolean colNameExists = false;
                    for (List<String> prvField : fields) {
                        if (prvField.contains(colName)) {
                            colNameExists = true;
                            break;
                        }
                    }
                    if (!colNameExists) {
                        fieldNameList.add(colName);
                    }
                }
                fields.add(fieldNameList);
                String truncatedTableName =
                    tableName.substring(tableName.lastIndexOf(".") + 1);
                truncatedTableList.add(truncatedTableName);
                tableFieldMap.put(truncatedTableName, fieldNameList);
            }
            return new TabularRowSet(tableFieldMap, truncatedTableList);
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException ignored) {
                }
            }
            if (sqlConn != null) {
                try {
                    sqlConn.close();
                } catch (SQLException ignored) {
                }
            }
        }
    }

    static class Column {
        private final String name;
        private final String encodedName;
        private final String xsdType;

        Column(String name, int type) {
            this.name = name;

            // replace invalid XML element name, like " ", with "_x0020_" in
            // column headers, otherwise will generate a badly-formatted xml
            // doc.
            this.encodedName = XmlaUtil.encodeElementName(name);
            this.xsdType = sqlToXsdType(type);
        }
    }

    static class TabularRowSet implements QueryResult {
        private final List<Column> columns = new ArrayList<Column>();
        private final List<Object[]> rows;
        private int totalCount;

        /**
         * Creates a TabularRowSet based upon a SQL statement result.
         *
         * <p>Closes the SqlStatement when it is done.
         *
         * @param stmt SqlStatement
         * @param totalCount Total number of rows. If >= 0, writes the
         *   "totalCount" attribute into the XMLA response.
         */
        public TabularRowSet(
            SqlStatement stmt,
            int totalCount)
        {
            this.totalCount = totalCount;
            ResultSet rs = stmt.getResultSet();
            try {
                ResultSetMetaData md = rs.getMetaData();
                int columnCount = md.getColumnCount();

                // populate column defs
                for (int i = 0; i < columnCount; i++) {
                    columns.add(
                        new Column(
                            md.getColumnLabel(i + 1),
                            md.getColumnType(i + 1)));
                }

                // Populate data; assume that SqlStatement is already positioned
                // on first row (or isDone() is true), and assume that the
                // number of rows returned is limited.
                rows = new ArrayList<Object[]>();
                final List<SqlStatement.Accessor> accessors =
                    stmt.getAccessors();
                if (!stmt.isDone()) {
                    do {
                        Object[] row = new Object[columnCount];
                        for (int i = 0; i < columnCount; i++) {
                            row[i] = accessors.get(i).get();
                        }
                        rows.add(row);
                    } while (rs.next());
                }
            } catch (SQLException e) {
                throw stmt.handle(e);
            } finally {
                stmt.close();
            }
        }

        /**
         * Alternate constructor for advanced drill-through.
         *
         * @param tableFieldMap Map from table name to a list of the names of
         *      the fields in the table
         * @param tableList List of table names
         */
        public TabularRowSet(
            Map<String, List<String>> tableFieldMap, List<String> tableList)
        {
            for (String tableName : tableList) {
                List<String> fieldNames = tableFieldMap.get(tableName);
                for (String fieldName : fieldNames) {
                    columns.add(
                        new Column(
                            tableName + "." + fieldName,
                            Types.VARCHAR)); // don't know the real type
                }
            }

            rows = new ArrayList<Object[]>();
            Object[] row = new Object[columns.size()];
            for (int k = 0; k < row.length; k++) {
                row[k] = k;
            }
            rows.add(row);
        }

        public void unparse(SaxWriter writer) throws SAXException {
            // write total count row if enabled
            if (totalCount >= 0) {
                String countStr = Integer.toString(totalCount);
                writer.startElement("row");
                for (Column column : columns) {
                    writer.startElement(column.encodedName);
                    writer.characters(countStr);
                    writer.endElement();
                }
                writer.endElement(); // row
            }

            for (Object[] row : rows) {
                writer.startElement("row");
                for (int i = 0; i < row.length; i++) {
                    writer.startElement(columns.get(i).encodedName);
                    Object value = row[i];
                    if (value == null) {
                        writer.characters("null");
                    } else {
                        String valueString = value.toString();
                        if (value instanceof Number) {
                            valueString =
                                XmlaUtil.normalizeNumericString(valueString);
                        }
                        writer.characters(valueString);
                    }
                    writer.endElement();
                }
                writer.endElement(); // row
            }
        }

        /**
         * Writes the tabular drillthrough schema
         *
         * @param writer Writer
         */
        public void metadata(SaxWriter writer) {
            writer.startElement(
                "xsd:schema",
                "xmlns:xsd", NS_XSD,
                "targetNamespace", NS_XMLA_ROWSET,
                "xmlns", NS_XMLA_ROWSET,
                "xmlns:xsi", NS_XSI,
                "xmlns:sql", NS_XML_SQL,
                "elementFormDefault", "qualified");

            { // <root>
                writer.startElement(
                    "xsd:element",
                    "name", "root");
                writer.startElement("xsd:complexType");
                writer.startElement("xsd:sequence");
                writer.element(
                    "xsd:element",
                    "maxOccurs", "unbounded",
                    "minOccurs", 0,
                    "name", "row",
                    "type", "row");
                writer.endElement(); // xsd:sequence
                writer.endElement(); // xsd:complexType
                writer.endElement(); // xsd:element name=root
            }

            { // xsd:simpleType name="uuid"
                writer.startElement(
                    "xsd:simpleType",
                        "name", "uuid");
                writer.startElement(
                    "xsd:restriction",
                    "base", XSD_STRING);
                writer.element(
                    "xsd:pattern",
                    "value", RowsetDefinition.UUID_PATTERN);
                writer.endElement(); // xsd:restriction
                writer.endElement(); // xsd:simpleType
            }

            { // xsd:complexType name="row"
                writer.startElement(
                    "xsd:complexType",
                    "name", "row");
                writer.startElement("xsd:sequence");
                for (Column column : columns) {
                    writer.element(
                        "xsd:element",
                        "minOccurs", 0,
                        "name", column.encodedName,
                        "sql:field", column.name,
                        "type", column.xsdType);
                }

                writer.endElement(); // xsd:sequence
                writer.endElement(); // xsd:complexType
            }
            writer.endElement(); // xsd:schema
        }
    }

    /**
     * Converts a SQL type to XSD type.
     *
     * @param sqlType SQL type
     * @return XSD type
     */
    private static String sqlToXsdType(int sqlType) {
        switch (sqlType) {
        // Integer
        case Types.INTEGER:
        case Types.BIGINT:
        case Types.SMALLINT:
        case Types.TINYINT:
            return XSD_INTEGER;
        case Types.NUMERIC:
            return XSD_DECIMAL;
            // Real
        case Types.DOUBLE:
        case Types.FLOAT:
            return XSD_DOUBLE;
            // Date and time
        case Types.TIME:
        case Types.TIMESTAMP:
        case Types.DATE:
            return XSD_STRING;
            // Other
        default:
            return XSD_STRING;
        }
    }

    private QueryResult executeQuery(XmlaRequest request)
        throws XmlaException
    {
        final String statement = request.getStatement();

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("mdx: \"" + statement + "\"");
        }

        if ((statement == null) || (statement.length() == 0)) {
            return null;
        } else {
            checkFormat(request);

            DataSourcesConfig.DataSource ds = getDataSource(request);
            DataSourcesConfig.Catalog dsCatalog = getCatalog(request, ds, true);
            String roleName = request.getRoleName();
            Role role = request.getRole();

            final Connection connection =
                getConnection(dsCatalog, role, roleName);

            final Query query;
            try {
                query = connection.parseQuery(statement);
                query.setResultStyle(ResultStyle.LIST);
            } catch (XmlaException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new XmlaException(
                    CLIENT_FAULT_FC,
                    HSB_PARSE_QUERY_CODE,
                    HSB_PARSE_QUERY_FAULT_FS,
                    ex);
            }
            final Result result;
            try {
                result = connection.execute(query);
            } catch (XmlaException ex) {
                throw ex;
            } catch (Exception ex) {
                throw new XmlaException(
                    SERVER_FAULT_FC,
                    HSB_EXECUTE_QUERY_CODE,
                    HSB_EXECUTE_QUERY_FAULT_FS,
                    ex);
            }

            final Format format = getFormat(request, null);
            final Content content = getContent(request);
            final Enumeration.ResponseMimeType responseMimeType =
                getResponseMimeType(request);
            if (format == Format.Multidimensional) {
                return new MDDataSet_Multidimensional(
                    result,
                    content != Content.DataIncludeDefaultSlicer,
                    responseMimeType == Enumeration.ResponseMimeType.JSON);
            } else {
                return new MDDataSet_Tabular(result);
            }
        }
    }

    private static Format getFormat(
        XmlaRequest request,
        Format defaultValue)
    {
        final String formatName =
            request.getProperties().get(
                PropertyDefinition.Format.name());
        return Util.lookup(
            Format.class,
            formatName, defaultValue);
    }

    private static Content getContent(XmlaRequest request) {
        final String contentName =
            request.getProperties().get(
                PropertyDefinition.Content.name());
        return Util.lookup(
            Content.class,
            contentName,
            Content.DEFAULT);
    }

    private static Enumeration.ResponseMimeType getResponseMimeType(
        XmlaRequest request)
    {
        Enumeration.ResponseMimeType mimeType =
            Enumeration.ResponseMimeType.MAP.get(
                request.getProperties().get(
                    PropertyDefinition.ResponseMimeType.name()));
        if (mimeType == null) {
            mimeType = Enumeration.ResponseMimeType.SOAP;
        }
        return mimeType;
    }

    static abstract class MDDataSet implements QueryResult {
        protected final Result result;

        protected static final String[] cellProps = new String[] {
            "Value",
            "FmtValue",
            "FormatString"};

        protected static final Property[] cellPropLongs = {
            Property.VALUE,
            Property.FORMATTED_VALUE,
            Property.FORMAT_STRING};

        protected static final String[] defaultProps = new String[] {
            "UName",
            "Caption",
            "LName",
            "LNum",
            "DisplayInfo",
            // Not in spec nor generated by SQL Server
//            "Depth"
            };
        protected static final Map<String, String> longPropNames =
            new HashMap<String, String>();

        static {
            longPropNames.put("UName", Property.MEMBER_UNIQUE_NAME.name);
            longPropNames.put("Caption", Property.MEMBER_CAPTION.name);
            longPropNames.put("LName", Property.LEVEL_UNIQUE_NAME.name);
            longPropNames.put("LNum", Property.LEVEL_NUMBER.name);
            longPropNames.put("DisplayInfo", Property.DISPLAY_INFO.name);
        }

        protected MDDataSet(Result result) {
            this.result = result;
        }
    }

    static class MDDataSet_Multidimensional extends MDDataSet {
        private List<Hierarchy> slicerAxisHierarchies;
        private final boolean omitDefaultSlicerInfo;
        private final boolean json;

        protected MDDataSet_Multidimensional(
            Result result,
            boolean omitDefaultSlicerInfo,
            boolean json)
        {
            super(result);
            this.omitDefaultSlicerInfo = omitDefaultSlicerInfo;
            this.json = json;
        }

        public void unparse(SaxWriter writer) throws SAXException {
            olapInfo(writer);
            axes(writer);
            cellData(writer);
        }

        private void olapInfo(SaxWriter writer) {
            // What are all of the cube's hierachies
            Cube cube = result.getQuery().getCube();

            writer.startElement("OlapInfo");
            writer.startElement("CubeInfo");
            writer.startElement("Cube");
            writer.textElement("CubeName", cube.getName());
            writer.endElement();
            writer.endElement(); // CubeInfo

            // create AxesInfo for axes
            // -----------
            writer.startSequence("AxesInfo", "AxisInfo");
            final Axis[] axes = result.getAxes();
            final QueryAxis[] queryAxes = result.getQuery().getAxes();
            //axisInfo(writer, result.getSlicerAxis(), "SlicerAxis");
            List<Hierarchy> axisHierarchyList = new ArrayList<Hierarchy>();
            for (int i = 0; i < axes.length; i++) {
                List<Hierarchy> hiers =
                    axisInfo(writer, axes[i], queryAxes[i], "Axis" + i);
                axisHierarchyList.addAll(hiers);
            }
            ///////////////////////////////////////////////
            // create AxesInfo for slicer axes
            //
            List<Hierarchy> hierarchies;
            final QueryAxis slicerQueryAxis = result.getQuery().getSlicerAxis();
            if (omitDefaultSlicerInfo) {
                Axis slicerAxis = result.getSlicerAxis();
                // only add slicer axis element to response
                // if something is on the slicer
                if (slicerAxis.getPositions().get(0).size() > 0) {
                    hierarchies =
                        axisInfo(
                            writer, slicerAxis, slicerQueryAxis, "SlicerAxis");
                } else {
                    hierarchies = new ArrayList<Hierarchy>();
                }
            } else {
                // The slicer axes contains the default hierarchy
                // of each dimension not seen on another axis.
                List<Dimension> unseenDimensionList =
                new ArrayList<Dimension>(Arrays.asList(cube.getDimensions()));
                for (Hierarchy hier1 : axisHierarchyList) {
                    unseenDimensionList.remove(hier1.getDimension());
                }
                hierarchies = new ArrayList<Hierarchy>();
                for (Dimension dimension : unseenDimensionList) {
                    hierarchies.add(dimension.getHierarchy());
                }
                writer.startElement(
                    "AxisInfo",
                    "name", "SlicerAxis");
                writeHierarchyInfo(
                    writer, hierarchies, getProps(slicerQueryAxis));
                writer.endElement(); // AxisInfo
            }
            slicerAxisHierarchies = hierarchies;
            //
            ///////////////////////////////////////////////

            writer.endSequence(); // AxesInfo

            // -----------
            writer.startElement("CellInfo");
            cellProperty(writer, Property.VALUE, true, "Value");
            cellProperty(writer, Property.FORMATTED_VALUE, true, "FmtValue");
            cellProperty(writer, Property.FORMAT_STRING, true, "FormatString");
            cellProperty(writer, Property.LANGUAGE, false, "Language");
            cellProperty(writer, Property.BACK_COLOR, false, "BackColor");
            cellProperty(writer, Property.FORE_COLOR, false, "ForeColor");
            cellProperty(writer, Property.FONT_FLAGS, false, "FontFlags");
            writer.endElement(); // CellInfo
            // -----------
            writer.endElement(); // OlapInfo
        }

        private void cellProperty(
            SaxWriter writer,
            Property cellProperty,
            boolean evenEmpty,
            String elementName)
        {
            if (shouldReturnCellProperty(cellProperty, evenEmpty)) {
                writer.element(
                    elementName,
                    "name", cellProperty.name);
            }
        }

        private List<Hierarchy> axisInfo(
            SaxWriter writer,
            Axis axis,
            QueryAxis queryAxis,
            String axisName)
        {
            writer.startElement(
                "AxisInfo",
                "name", axisName);

            List<Hierarchy> hierarchies;
            Iterator<Position> it = axis.getPositions().iterator();
            if (it.hasNext()) {
                final Position position = it.next();
                hierarchies = new ArrayList<Hierarchy>();
                for (Member member : position) {
                    hierarchies.add(member.getHierarchy());
                }
            } else {
                hierarchies = Collections.emptyList();
                //final QueryAxis queryAxis = this.result.getQuery().axes[i];
                // TODO:
            }
            String[] props = getProps(queryAxis);
            writeHierarchyInfo(writer, hierarchies, props);

            writer.endElement(); // AxisInfo

            return hierarchies;
        }

        private void writeHierarchyInfo(
            SaxWriter writer,
            List<Hierarchy> hierarchies,
            String[] props)
        {
            writer.startSequence(null, "HierarchyInfo");
            for (Hierarchy hierarchy : hierarchies) {
                writer.startElement(
                    "HierarchyInfo",
                    "name", hierarchy.getName());
                for (final String prop : props) {
                    final String encodedProp = XmlaUtil.encodeElementName(prop);
                    writer.element(
                        encodedProp, getAttributes(encodedProp, hierarchy));
                }
                writer.endElement(); // HierarchyInfo
            }
            writer.endSequence(); // "HierarchyInfo"
        }

        private Object[] getAttributes(String prop, Hierarchy hierarchy) {
            String actualPropName = getPropertyName(prop);
            List<Object> values = new ArrayList<Object>();
            values.add("name");
            values.add(
                hierarchy.getUniqueName() + "." + Util.quoteMdxIdentifier(
                    actualPropName));
            if (longPropNames.get(prop) == null) {
                //Adding type attribute to the optional properties
                values.add("type");
                values.add(getXsdType(actualPropName));
            }
            return values.toArray();
        }

        private String getXsdType(String prop) {
            final Property property = Property.lookup(prop, false);
            if (property != null) {
                Property.Datatype datatype = property.getType();
                switch (datatype) {
                case TYPE_NUMERIC:
                    return RowsetDefinition.Type.UnsignedInteger.columnType;
                case TYPE_BOOLEAN:
                    return RowsetDefinition.Type.Boolean.columnType;
                }
            }
            return RowsetDefinition.Type.String.columnType;
        }

        private String getPropertyName(String prop) {
            String actualPropertyName = longPropNames.get(prop);
            if (actualPropertyName == null) {
                return prop;
            }
            return actualPropertyName;
        }

        private void axes(SaxWriter writer) {
            writer.startSequence("Axes", "Axis");
            //axis(writer, result.getSlicerAxis(), "SlicerAxis");
            final Axis[] axes = result.getAxes();
            final QueryAxis[] queryAxes = result.getQuery().getAxes();
            for (int i = 0; i < axes.length; i++) {
                final String[] props = getProps(queryAxes[i]);
                axis(writer, axes[i], props, "Axis" + i);
            }

            ////////////////////////////////////////////
            // now generate SlicerAxis information
            //
            if (omitDefaultSlicerInfo) {
                final QueryAxis slicerQueryAxis =
                    result.getQuery().getSlicerAxis();
                Axis slicerAxis = result.getSlicerAxis();
                // only add slicer axis element to response
                // if something is on the slicer
                if (slicerAxis.getPositions().get(0).size() > 0) {
                    axis(
                        writer,
                        result.getSlicerAxis(),
                        getProps(slicerQueryAxis),
                        "SlicerAxis");
                }
            } else {
                List<Hierarchy> hierarchies = slicerAxisHierarchies;
                writer.startElement(
                    "Axis",
                    "name", "SlicerAxis");
                writer.startSequence("Tuples", "Tuple");
                writer.startSequence("Tuple", "Member");

                Map<String, Integer> memberMap = new HashMap<String, Integer>();
                Member positionMember;
                Axis slicerAxis = result.getSlicerAxis();
                if (slicerAxis.getPositions() != null
                    && slicerAxis.getPositions().size() > 0)
                {
                    final Position pos0 = slicerAxis.getPositions().get(0);
                    int i = 0;
                    for (Member member : pos0) {
                        memberMap.put(member.getHierarchy().getName(), i++);
                    }
                }

                final QueryAxis slicerQueryAxis =
                    result.getQuery().getSlicerAxis();
                final List<Member> slicerMembers =
                    result.getSlicerAxis().getPositions().get(0);
                for (Hierarchy hierarchy : hierarchies) {
                    // Find which member is on the slicer.
                    // If it's not explicitly there, use the default member.
                    Member member = hierarchy.getDefaultMember();
                    final Integer indexPosition =
                        memberMap.get(hierarchy.getName());
                    if (indexPosition != null) {
                        positionMember =
                            slicerAxis.getPositions().get(0).get(indexPosition);
                    } else {
                        positionMember = null;
                    }
                    for (Member slicerMember : slicerMembers) {
                        if (slicerMember.getHierarchy().equals(hierarchy)) {
                            member = slicerMember;
                            break;
                        }
                    }

                    if (member != null) {
                        if (positionMember != null) {
                            writeMember(
                                writer, positionMember, null,
                                slicerAxis.getPositions().get(0), indexPosition,
                                getProps(slicerQueryAxis));
                        } else {
                            slicerAxis(
                                writer, member, getProps(slicerQueryAxis));
                        }
                    } else {
                        LOGGER.warn(
                            "Can not create SlicerAxis: "
                            + "null default member for Hierarchy "
                            + hierarchy.getUniqueName());
                    }
                }
                writer.endSequence(); // Tuple
                writer.endSequence(); // Tuples
                writer.endElement(); // Axis
            }

            //
            ////////////////////////////////////////////

            writer.endSequence(); // Axes
        }

        private String[] getProps(QueryAxis queryAxis) {
            if (queryAxis == null) {
                return defaultProps;
            }
            Id[] dimensionProperties = queryAxis.getDimensionProperties();
            if (dimensionProperties.length == 0) {
                return defaultProps;
            }
            String[] props =
                new String[defaultProps.length + dimensionProperties.length];
            System.arraycopy(defaultProps, 0, props, 0, defaultProps.length);
            for (int i = 0; i < dimensionProperties.length; i++) {
                // If a property is compound [Foo].[Bar], use only the last
                // segment "Bar".
                final List<Id.Segment> segmentList =
                    dimensionProperties[i].getSegments();
                props[defaultProps.length + i] =
                    segmentList.get(segmentList.size() - 1).name;
            }
            return props;
        }

        private void axis(
            SaxWriter writer,
            Axis axis,
            String[] props,
            String axisName)
        {
            writer.startElement(
                "Axis",
                "name", axisName);
            writer.startSequence("Tuples", "Tuple");

            List<Position> positions = axis.getPositions();
            Iterator<Position> pit = positions.iterator();
            Position prevPosition = null;
            Position position = pit.hasNext() ? pit.next() : null;
            Position nextPosition = pit.hasNext() ? pit.next() : null;
            while (position != null) {
                writer.startSequence("Tuple", "Member");
                int k = 0;
                for (Member member : position) {
                    writeMember(
                        writer, member, prevPosition, nextPosition, k++, props);
                }
                writer.endSequence(); // Tuple
                prevPosition = position;
                position = nextPosition;
                nextPosition = pit.hasNext() ? pit.next() : null;
            }
            writer.endSequence(); // Tuples
            writer.endElement(); // Axis
        }

        private void writeMember(
            SaxWriter writer,
            Member member,
            Position prevPosition,
            Position nextPosition,
            int k,
            String[] props)
        {
            writer.startElement(
                "Member",
                "Hierarchy", member.getHierarchy().getName());
            for (String prop : props) {
                Object value;
                String propLong = longPropNames.get(prop);
                if (propLong == null) {
                    propLong = prop;
                }
                if (propLong.equals(Property.DISPLAY_INFO.name)) {
                    Integer childrenCard = (Integer) member
                      .getPropertyValue(Property.CHILDREN_CARDINALITY.name);
                    value = calculateDisplayInfo(
                        prevPosition,
                        nextPosition,
                        member, k, childrenCard);
                } else if (propLong.equals(Property.DEPTH.name)) {
                    value = member.getDepth();
                } else {
                    value = member.getPropertyValue(propLong);
                }
                if (value != null) {
                    writer.textElement(XmlaUtil.encodeElementName(prop), value);
                }
            }

            writer.endElement(); // Member
        }

        private void slicerAxis(
            SaxWriter writer, Member member, String[] props)
        {
            writer.startElement(
                "Member",
                "Hierarchy", member.getHierarchy().getName());
            for (String prop : props) {
                Object value;
                String propLong = longPropNames.get(prop);
                if (propLong == null) {
                    propLong = prop;
                }
                if (propLong.equals(Property.DISPLAY_INFO.name)) {
                    Integer childrenCard =
                        (Integer) member.getPropertyValue(
                            Property.CHILDREN_CARDINALITY.name);
                    // NOTE: don't know if this is correct for
                    // SlicerAxis
                    int displayInfo = 0xffff & childrenCard;
/*
                    int displayInfo =
                        calculateDisplayInfo((j == 0 ? null : positions[j - 1]),
                          (j + 1 == positions.length ? null : positions[j + 1]),
                          member, k, childrenCard.intValue());
*/
                    value = displayInfo;
                } else if (propLong.equals(Property.DEPTH.name)) {
                    value = member.getDepth();
                } else {
                    value = member.getPropertyValue(propLong);
                }
                if (value != null) {
                    writer.textElement(prop, value);
                }
            }
            writer.endElement(); // Member
        }

        private int calculateDisplayInfo(
            Position prevPosition, Position nextPosition,
            Member currentMember, int memberOrdinal, int childrenCount)
        {
            int displayInfo = 0xffff & childrenCount;

            if (nextPosition != null) {
                String currentUName = currentMember.getUniqueName();
                Member nextMember = nextPosition.get(memberOrdinal);
                String nextParentUName = nextMember.getParentUniqueName();
                if (currentUName.equals(nextParentUName)) {
                    displayInfo |= 0x10000;
                }
            }
            if (prevPosition != null) {
                String currentParentUName = currentMember.getParentUniqueName();
                Member prevMember = prevPosition.get(memberOrdinal);
                String prevParentUName = prevMember.getParentUniqueName();
                if (currentParentUName != null
                    && currentParentUName.equals(prevParentUName))
                {
                    displayInfo |= 0x20000;
                }
            }
            return displayInfo;
        }

        private void cellData(SaxWriter writer) {
            writer.startSequence("CellData", "Cell");
            final int axisCount = result.getAxes().length;
            int[] pos = new int[axisCount];
            int[] cellOrdinal = new int[] {0};

            Evaluator evaluator = RolapUtil.createEvaluator(result.getQuery());
            int axisOrdinal = axisCount - 1;
            recurse(writer, pos, axisOrdinal, evaluator, cellOrdinal);

            writer.endSequence(); // CellData
        }

        private void recurse(
            SaxWriter writer, int[] pos,
            int axisOrdinal, Evaluator evaluator, int[] cellOrdinal)
        {
            if (axisOrdinal < 0) {
                emitCell(writer, pos, evaluator, cellOrdinal[0]++);

            } else {
                Axis axis = result.getAxes()[axisOrdinal];
                List<Position> positions = axis.getPositions();
                int i = 0;
                for (Position position : positions) {
                    pos[axisOrdinal] = i;
                    evaluator.setContext(position);
                    recurse(
                        writer, pos, axisOrdinal - 1, evaluator, cellOrdinal);
                    i++;
                }
            }
        }

        private void emitCell(
            SaxWriter writer, int[] pos,
            Evaluator evaluator, int ordinal)
        {
            Cell cell = result.getCell(pos);
            if (cell.isNull() && ordinal != 0) {
                // Ignore null cell like MS AS, except for Oth ordinal
                return;
            }

            writer.startElement(
                "Cell",
                "CellOrdinal", ordinal);
            for (int i = 0; i < cellProps.length; i++) {
                Property cellPropLong = cellPropLongs[i];
                Object value = cell.getPropertyValue(cellPropLong.name);
                if (value == null) {
                    continue;
                }
                if (!shouldReturnCellProperty(cellPropLong, true)) {
                    continue;
                }

                if (!json && cellPropLong == Property.VALUE) {
                    if (cell.isNull()) {
                        // Return cell without value as in case of AS2005
                        continue;
                    }
                    final String dataType = (String)
                        evaluator.getProperty(
                            Property.DATATYPE.getName(), null);
                    final ValueInfo vi = new ValueInfo(dataType, value);
                    final String valueType = vi.valueType;
                    final String valueString;
                    if (vi.isDecimal) {
                        valueString =
                            XmlaUtil.normalizeNumericString(
                                vi.value.toString());
                    } else {
                        valueString = vi.value.toString();
                    }

                    writer.startElement(
                        cellProps[i],
                        "xsi:type", valueType);
                    writer.characters(valueString);
                    writer.endElement();
                } else {
                    writer.textElement(cellProps[i], value);
                }
            }
            writer.endElement(); // Cell
        }

        /**
         * Returns whether we should return a cell property in the XMLA result.
         *
         * @param cellProperty Cell property definition
         * @param evenEmpty Whether to return even if cell has no properties
         * @return Whether to return cell property in XMLA result
         */
        private boolean shouldReturnCellProperty(
            Property cellProperty,
            boolean evenEmpty)
        {
            Query query = result.getQuery();
            return
                (evenEmpty
                 && query.isCellPropertyEmpty())
                || query.hasCellProperty(cellProperty.name);
        }
    }

    static abstract class ColumnHandler {
        protected final String name;
        protected final String encodedName;

        protected ColumnHandler(String name) {
            this.name = name;
            this.encodedName = XmlaUtil.encodeElementName(this.name);
        }

        abstract void write(SaxWriter writer, Cell cell, Member[] members);
        abstract void metadata(SaxWriter writer);
    }


    /**
     * Callback to handle one column, representing the combination of a
     * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME])
     * in a flattened dataset.
     */
    static class CellColumnHandler extends ColumnHandler {

        CellColumnHandler(String name) {
            super(name);
        }

        public void metadata(SaxWriter writer) {
            writer.element(
                "xsd:element",
                "minOccurs", 0,
                "name", encodedName,
                "sql:field", name);
        }

        public void write(
            SaxWriter writer, Cell cell, Member[] members)
        {
            if (cell.isNull()) {
                return;
            }
            Object value = cell.getValue();
/*
            String valueString = value.toString();
            String valueType = deduceValueType(cell, value);

            writer.startElement(encodedName,
                "xsi:type", valueType});
            if (value instanceof Number) {
                valueString = XmlaUtil.normalizeNumericString(valueString);
            }
            writer.characters(valueString);
            writer.endElement();
*/
            final String dataType = (String)
                    cell.getPropertyValue(Property.DATATYPE.getName());

            final ValueInfo vi = new ValueInfo(dataType, value);
            final String valueType = vi.valueType;
            value = vi.value;
            boolean isDecimal = vi.isDecimal;

            String valueString = value.toString();

            writer.startElement(
                encodedName,
                "xsi:type", valueType);
            if (isDecimal) {
                valueString = XmlaUtil.normalizeNumericString(valueString);
            }
            writer.characters(valueString);
            writer.endElement();
        }
    }

    /**
     * Callback to handle one column, representing the combination of a
     * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME])
     * in a flattened dataset.
     */
    static class MemberColumnHandler extends ColumnHandler {
        private final String property;
        private final Level level;
        private final int memberOrdinal;

        public MemberColumnHandler(
            String property, Level level, int memberOrdinal)
        {
            super(
                level.getUniqueName() + "."
                + Util.quoteMdxIdentifier(property));
            this.property = property;
            this.level = level;
            this.memberOrdinal = memberOrdinal;
        }

        public void metadata(SaxWriter writer) {
            writer.element(
                "xsd:element",
                "minOccurs", 0,
                "name", encodedName,
                "sql:field", name,
                "type", XSD_STRING);
        }

        public void write(
            SaxWriter writer, Cell cell, Member[] members)
        {
            Member member = members[memberOrdinal];
            final int depth = level.getDepth();
            if (member.getDepth() < depth) {
                // This column deals with a level below the current member.
                // There is no value to write.
                return;
            }
            while (member.getDepth() > depth) {
                member = member.getParentMember();
            }
            final Object propertyValue = member.getPropertyValue(property);
            if (propertyValue == null) {
                return;
            }

            writer.startElement(encodedName);
            writer.characters(propertyValue.toString());
            writer.endElement();
        }
    }

    static class MDDataSet_Tabular extends MDDataSet {
        private final boolean empty;
        private final int[] pos;
        private final int axisCount;
        private int cellOrdinal;

        private static final Id[] MemberCaptionIdArray = {
            new Id(
                new Id.Segment(
                    Property.MEMBER_CAPTION.name,
                    Id.Quoting.QUOTED))
        };
        private final Member[] members;
        private final ColumnHandler[] columnHandlers;

        public MDDataSet_Tabular(Result result) {
            super(result);
            final Axis[] axes = result.getAxes();
            axisCount = axes.length;
            pos = new int[axisCount];

            // Count dimensions, and deduce list of levels which appear on
            // non-COLUMNS axes.
            boolean empty = false;
            int dimensionCount = 0;
            for (int i = axes.length - 1; i > 0; i--) {
                Axis axis = axes[i];
                if (axis.getPositions().size() == 0) {
                    // If any axis is empty, the whole data set is empty.
                    empty = true;
                    continue;
                }
                dimensionCount += axis.getPositions().get(0).size();
            }
            this.empty = empty;

            // Build a list of the lowest level used on each non-COLUMNS axis.
            Level[] levels = new Level[dimensionCount];
            List<ColumnHandler> columnHandlerList =
                new ArrayList<ColumnHandler>();
            int memberOrdinal = 0;
            if (!empty) {
                for (int i = axes.length - 1; i > 0; i--) {
                    final Axis axis = axes[i];
                    final QueryAxis queryAxis = result.getQuery().getAxes()[i];
                    final int z0 = memberOrdinal; // save ordinal so can rewind
                    final List<Position> positions = axis.getPositions();
                    int jj = 0;
                    for (Position position : positions) {
                        memberOrdinal = z0; // rewind to start
                        for (Member member : position) {
                            if (jj == 0
                                || member.getLevel().getDepth()
                                > levels[memberOrdinal].getDepth())
                            {
                                levels[memberOrdinal] = member.getLevel();
                            }
                            memberOrdinal++;
                        }
                        jj++;
                    }

                    // Now we know the lowest levels on this axis, add
                    // properties.
                    Id[] dimProps = queryAxis.getDimensionProperties();
                    if (dimProps.length == 0) {
                        dimProps = MemberCaptionIdArray;
                    }
                    for (int j = z0; j < memberOrdinal; j++) {
                        Level level = levels[j];
                        for (int k = 0; k <= level.getDepth(); k++) {
                            final Level level2 =
                                    level.getHierarchy().getLevels()[k];
                            if (level2.isAll()) {
                                continue;
                            }
                            for (Id dimProp : dimProps) {
                                columnHandlerList.add(
                                    new MemberColumnHandler(
                                        dimProp.toStringArray()[0],
                                        level2,
                                        j));
                            }
                        }
                    }
                }
            }
            this.members = new Member[memberOrdinal + 1];

            // Deduce the list of column headings.
            if (axes.length > 0) {
                Axis columnsAxis = axes[0];
                for (Position position : columnsAxis.getPositions()) {
                    String name = null;
                    int j = 0;
                    for (Member member : position) {
                        if (j == 0) {
                            name = member.getUniqueName();
                        } else {
                            name = name + "." + member.getUniqueName();
                        }
                        j++;
                    }
                    columnHandlerList.add(
                        new CellColumnHandler(name));
                }
            }

            this.columnHandlers =
                columnHandlerList.toArray(
                    new ColumnHandler[columnHandlerList.size()]);
        }

        public void metadata(SaxWriter writer) {
            // ADOMD wants a XSD even a void one.
//            if (empty) {
//                return;
//            }

            writer.startElement(
                "xsd:schema",
                "xmlns:xsd", NS_XSD,
                "targetNamespace", NS_XMLA_ROWSET,
                "xmlns", NS_XMLA_ROWSET,
                "xmlns:xsi", NS_XSI,
                "xmlns:sql", NS_XML_SQL,
                "elementFormDefault", "qualified");

            { // <root>
                writer.startElement(
                    "xsd:element",
                    "name", "root");
                writer.startElement("xsd:complexType");
                writer.startElement("xsd:sequence");
                writer.element(
                    "xsd:element",
                    "maxOccurs", "unbounded",
                    "minOccurs", 0,
                    "name", "row",
                    "type", "row");
                writer.endElement(); // xsd:sequence
                writer.endElement(); // xsd:complexType
                writer.endElement(); // xsd:element name=root
            }

            { // xsd:simpleType name="uuid"
                writer.startElement(
                    "xsd:simpleType",
                    "name", "uuid");
                writer.startElement(
                    "xsd:restriction",
                    "base", XSD_STRING);
                writer.element(
                    "xsd:pattern",
                    "value", RowsetDefinition.UUID_PATTERN);
                writer.endElement(); // xsd:restriction
                writer.endElement(); // xsd:simpleType
            }

            { // xsd:complexType name="row"
                writer.startElement(
                    "xsd:complexType",
                    "name", "row");
                writer.startElement("xsd:sequence");
                for (ColumnHandler columnHandler : columnHandlers) {
                    columnHandler.metadata(writer);
                }
                writer.endElement(); // xsd:sequence
                writer.endElement(); // xsd:complexType
            }
            writer.endElement(); // xsd:schema
        }

        public void unparse(SaxWriter writer) throws SAXException {
            if (empty) {
                return;
            }
            cellData(writer);
        }

        private void cellData(SaxWriter writer) throws SAXException {
            cellOrdinal = 0;
            iterate(writer);
        }

        /**
         * Iterates over the resust writing tabular rows.
         *
         * @param writer Writer
         * @throws org.xml.sax.SAXException on error
         */
        private void iterate(SaxWriter writer) throws SAXException {
            switch (axisCount) {
            case 0:
                // For MDX like: SELECT FROM Sales
                emitCell(writer, result.getCell(pos));
                return;
            default:
//                throw new SAXException("Too many axes: " + axisCount);
                iterate(writer, axisCount - 1, 0);
                break;
            }
        }

        private void iterate(SaxWriter writer, int axis, final int xxx) {
            final List<Position> positions =
                result.getAxes()[axis].getPositions();
            int axisLength = axis == 0 ? 1 : positions.size();

            for (int i = 0; i < axisLength; i++) {
                final Position position = positions.get(i);
                int ho = xxx;
                for (int j = 0;
                     j < position.size() && ho < members.length;
                     j++, ho++)
                {
                    members[ho] = position.get(j);
                }

                ++cellOrdinal;
                Util.discard(cellOrdinal);

                if (axis >= 2) {
                    iterate(writer, axis - 1, ho);
                } else {
                    writer.startElement("row");//abrimos la fila
                    pos[axis] = i; //coordenadas: fila i
                    pos[0] = 0; //coordenadas (0,i): columna 0
                    for (ColumnHandler columnHandler : columnHandlers) {
                        if (columnHandler instanceof MemberColumnHandler) {
                            columnHandler.write(writer, null, members);
                        } else if (columnHandler instanceof CellColumnHandler) {
                            columnHandler.write(
                                writer, result.getCell(pos), null);
                            pos[0]++;// next col.
                        }
                    }
                    writer.endElement();//cerramos la fila
                }
            }
        }

        private void emitCell(SaxWriter writer, Cell cell) {
            ++cellOrdinal;
            Util.discard(cellOrdinal);

            // Ignore empty cells.
            final Object cellValue = cell.getValue();
            if (cellValue == null) {
                return;
            }

            writer.startElement("row");
            for (ColumnHandler columnHandler : columnHandlers) {
                columnHandler.write(writer, cell, members);
            }
            writer.endElement();
        }
    }

    private void discover(XmlaRequest request, XmlaResponse response)
        throws XmlaException
    {
        final RowsetDefinition rowsetDefinition =
            RowsetDefinition.valueOf(request.getRequestType());
        Rowset rowset = rowsetDefinition.getRowset(request, this);

        Format format = getFormat(request, Format.Tabular);
        if (format != Format.Tabular) {
            throw new XmlaException(
                CLIENT_FAULT_FC,
                HSB_DISCOVER_FORMAT_CODE,
                HSB_DISCOVER_FORMAT_FAULT_FS,
                new UnsupportedOperationException(
                    "<Format>: only 'Tabular' allowed in Discover method "
                    + "type"));
        }
        final Content content = getContent(request);

        SaxWriter writer = response.getWriter();
        writer.startDocument();

        writer.startElement(
            prefix + ":DiscoverResponse",
            "xmlns:" + prefix, NS_XMLA);
        writer.startElement(prefix + ":return");
        writer.startElement(
            "root",
            "xmlns", NS_XMLA_ROWSET,
            "xmlns:xsi", NS_XSI,
            "xmlns:xsd", NS_XSD,
            "xmlns:EX", NS_XMLA_EX);

        if ((content == Content.Schema)
            || (content == Content.SchemaData))
        {
            rowset.rowsetDefinition.writeRowsetXmlSchema(writer);
        }

        try {
            if ((content == Content.Data)
                || (content == Content.SchemaData))
            {
                rowset.unparse(response);
            }
        } catch (XmlaException xex) {
            throw xex;
        } catch (Throwable t) {
            throw new XmlaException(
                SERVER_FAULT_FC,
                HSB_DISCOVER_UNPARSE_CODE,
                HSB_DISCOVER_UNPARSE_FAULT_FS,
                t);
        } finally {
            // keep the tags balanced, even if there's an error
            writer.endElement();
            writer.endElement();
            writer.endElement();
        }

        writer.endDocument();
    }

    /**
     * Gets a Connection given a catalog (and implicitly the catalog's data
     * source) and a user role.
     *
     * @param catalog Catalog
     * @param role User role
     * @param roleName User role name
     * @return Connection
     * @throws XmlaException If error occurs
     */
    protected Connection getConnection(
        final DataSourcesConfig.Catalog catalog,
        final Role role,
        final String roleName)
        throws XmlaException
    {
        DataSourcesConfig.DataSource ds = catalog.getDataSource();

        Util.PropertyList connectProperties =
            Util.parseConnectString(catalog.getDataSourceInfo());

        String catalogUrl = catalogLocator.locate(catalog.definition);

        if (LOGGER.isDebugEnabled()) {
            if (catalogUrl == null) {
                LOGGER.debug("XmlaHandler.getConnection: catalogUrl is null");
            } else {
                LOGGER.debug(
                    "XmlaHandler.getConnection: catalogUrl=" + catalogUrl);
            }
        }

        connectProperties.put(
            RolapConnectionProperties.Catalog.name(), catalogUrl);

        // Checking access
        if (!DataSourcesConfig.DataSource.AUTH_MODE_UNAUTHENTICATED
            .equalsIgnoreCase(
                ds.getAuthenticationMode())
            && (role == null)
            && (roleName == null))
        {
            throw new XmlaException(
                CLIENT_FAULT_FC,
                HSB_ACCESS_DENIED_CODE,
                HSB_ACCESS_DENIED_FAULT_FS,
                new SecurityException(
                    "Access denied for data source needing authentication"));
        }

        // Role in request overrides role in connect string, if present.
        if (roleName != null) {
            connectProperties.put(
                RolapConnectionProperties.Role.name(), roleName);
        }

        RolapConnection conn = (RolapConnection) DriverManager.getConnection(
                connectProperties, null);

        if (role != null) {
            conn.setRole(role);
        }

        if (LOGGER.isDebugEnabled()) {
            if (conn == null) {
                LOGGER.debug(
                    "XmlaHandler.getConnection: returning connection null");
            } else {
                LOGGER.debug(
                    "XmlaHandler.getConnection: returning connection not null");
            }
        }
        return conn;
    }

    /**
     * Returns the DataSource associated with the request property or null if
     * one was not specified.
     *
     * @param request Request
     * @return DataSource for this request
     * @throws XmlaException If error occurs
     */
    public DataSourcesConfig.DataSource getDataSource(XmlaRequest request)
        throws XmlaException
    {
        Map<String, String> properties = request.getProperties();
        final String dataSourceInfo =
            properties.get(PropertyDefinition.DataSourceInfo.name());
        if (!dataSourcesMap.containsKey(dataSourceInfo)) {
            throw new XmlaException(
                CLIENT_FAULT_FC,
                HSB_CONNECTION_DATA_SOURCE_CODE,
                HSB_CONNECTION_DATA_SOURCE_FAULT_FS,
                Util.newError(
                    "no data source is configured with name '"
                    + dataSourceInfo + "'"));
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                "XmlaHandler.getDataSource: dataSourceInfo="
                + dataSourceInfo);
        }

        final DataSourcesConfig.DataSource ds =
            dataSourcesMap.get(dataSourceInfo);
        if (LOGGER.isDebugEnabled()) {
            if (ds == null) {
                // TODO: this if a failure situation
                LOGGER.debug("XmlaHandler.getDataSource: ds is null");
            } else {
                LOGGER.debug(
                    "XmlaHandler.getDataSource: ds.dataSourceInfo="
                    + ds.getDataSourceInfo());
            }
        }
        return ds;
    }

    /**
     * Get the DataSourcesConfig.Catalog with the given catalog name from the
     * DataSource's catalogs if there is a match and otherwise return null.
     *
     * @param ds DataSource
     * @param catalogName Catalog name
     * @return DataSourcesConfig.Catalog or null
     */
    public DataSourcesConfig.Catalog getCatalog(
        DataSourcesConfig.DataSource ds,
        String catalogName)
    {
        DataSourcesConfig.Catalog[] catalogs = ds.catalogs.catalogs;
        if (catalogName == null) {
            // if there is no catalog name - its optional and there is
            // only one, then return it.
            if (catalogs.length == 1) {
                return catalogs[0];
            }
        } else {
            for (DataSourcesConfig.Catalog dsCatalog : catalogs) {
                if (catalogName.equals(dsCatalog.name)) {
                    return dsCatalog;
                }
            }
        }
        return null;
    }

    /**
     * Get array of DataSourcesConfig.Catalog returning only one entry if the
     * catalog was specified as a property in the request or all catalogs
     * associated with the Datasource if there was no catalog property.
     *
     * @param request Request
     * @param ds DataSource
     * @return Array of DataSourcesConfig.Catalog
     */
    public DataSourcesConfig.Catalog[] getCatalogs(
        XmlaRequest request,
        DataSourcesConfig.DataSource ds)
    {
        Map<String, String> properties = request.getProperties();
        final String catalogName =
            properties.get(PropertyDefinition.Catalog.name());
        if (catalogName != null) {
            DataSourcesConfig.Catalog dsCatalog = getCatalog(ds, catalogName);
            return new DataSourcesConfig.Catalog[] { dsCatalog };
        } else {
            // no catalog specified in Properties so return them all
            return ds.catalogs.catalogs;
        }
    }

    /**
     * Returns the DataSourcesConfig.Catalog associated with the
     * catalog name that is part of the request properties or
     * null if there is no catalog with that name.
     *
     * @param request Request
     * @param ds DataSource
     * @param required Whether to throw an error if catalog name is not
     * specified
     *
     * @return DataSourcesConfig Catalog or null
     * @throws XmlaException If error occurs
     */
    public DataSourcesConfig.Catalog getCatalog(
        XmlaRequest request,
        DataSourcesConfig.DataSource ds,
        boolean required)
        throws XmlaException
    {
        Map<String, String> properties = request.getProperties();
        final String catalogName =
            properties.get(PropertyDefinition.Catalog.name());
        DataSourcesConfig.Catalog dsCatalog = getCatalog(ds, catalogName);
        if (dsCatalog == null) {
            if (catalogName == null) {
                if (required) {
                    throw new XmlaException(
                        CLIENT_FAULT_FC,
                        HSB_CONNECTION_DATA_SOURCE_CODE,
                        HSB_CONNECTION_DATA_SOURCE_FAULT_FS,
                        Util.newError("catalog not specified"));
                }
                return null;
            }
            throw new XmlaException(
                CLIENT_FAULT_FC,
                HSB_CONNECTION_DATA_SOURCE_CODE,
                HSB_CONNECTION_DATA_SOURCE_FAULT_FS,
                Util.newError("no catalog named '" + catalogName + "'"));
        }
        return dsCatalog;
    }

    public static void main(String[] args) {
        for (RowsetDefinition def : RowsetDefinition.values()) {
            System.out.print("    " + def.name() + "(");
            int k = 0;
            for (RowsetDefinition.Column column : def.columnDefinitions) {
                if (k++ == 0) {
                    System.out.println();
                } else {
                    System.out.println(",");
                }
                System.out.print(
                    "        new MetadataColumn(\"" + column.name + "\")");
            }
            System.out.println("),");
        }
    }
}

// End XmlaHandler.java
TOP

Related Classes of mondrian.xmla.XmlaHandler$MDDataSet_Tabular

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.