001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2003-2005 Julian Hyde
008// Copyright (C) 2005-2013 Pentaho
009// All Rights Reserved.
010*/
011package mondrian.xmla;
012
013import mondrian.olap.MondrianProperties;
014import mondrian.olap.Util;
015import mondrian.util.CompositeList;
016import mondrian.xmla.impl.DefaultSaxWriter;
017
018import org.apache.log4j.Logger;
019
020import org.olap4j.*;
021import org.olap4j.impl.Olap4jUtil;
022import org.olap4j.metadata.*;
023import org.olap4j.metadata.Property.StandardCellProperty;
024import org.olap4j.metadata.Property.StandardMemberProperty;
025
026import org.xml.sax.SAXException;
027
028import java.io.PrintWriter;
029import java.io.StringWriter;
030import java.lang.reflect.InvocationTargetException;
031import java.lang.reflect.UndeclaredThrowableException;
032import java.math.BigDecimal;
033import java.math.BigInteger;
034import java.sql.*;
035import java.util.*;
036import java.util.Date;
037
038import static mondrian.xmla.XmlaConstants.*;
039import static org.olap4j.metadata.XmlaConstants.*;
040
041/**
042 * An <code>XmlaHandler</code> responds to XML for Analysis (XML/A) requests.
043 *
044 * @author jhyde, Gang Chen
045 * @since 27 April, 2003
046 */
047public class XmlaHandler {
048    private static final Logger LOGGER = Logger.getLogger(XmlaHandler.class);
049
050    /**
051     * Name of property used by JDBC to hold user name.
052     */
053    private static final String JDBC_USER = "user";
054
055    /**
056     * Name of property used by JDBC to hold password.
057     */
058    private static final String JDBC_PASSWORD = "password";
059
060    /**
061     * Name of property used by JDBC to hold locale. It is not hard-wired into
062     * DriverManager like "user" and "password", but we do expect any olap4j
063     * driver that supports i18n to use this property name.
064     */
065    public static final String JDBC_LOCALE = "locale";
066
067    final ConnectionFactory connectionFactory;
068    private final String prefix;
069
070    public static XmlaExtra getExtra(OlapConnection connection) {
071        try {
072            final XmlaExtra extra = connection.unwrap(XmlaExtra.class);
073            if (extra != null) {
074                return extra;
075            }
076        } catch (SQLException e) {
077            // Connection cannot provide an XmlaExtra. Fall back and give a
078            // default implementation.
079        } catch (UndeclaredThrowableException ute) {
080            //
081            // Note: this is necessary because we use a dynamic proxy for the
082            // connection.
083            // I could not catch and un-wrap the Undeclared Throwable within
084            // the proxy.
085            // The exception comes out here and I couldn't find any better
086            // ways to deal with it.
087            //
088            // The undeclared throwable contains an Invocation Target Exception
089            // which in turns contains the real exception thrown by the "unwrap"
090            // method, for example OlapException.
091            //
092
093            Throwable cause = ute.getCause();
094            if (cause instanceof InvocationTargetException) {
095                cause = cause.getCause();
096            }
097
098            // this maintains the original behaviour: don't catch exceptions
099            // that are not subclasses of SQLException
100
101            if (! (cause instanceof SQLException)) {
102                throw ute;
103            }
104        }
105        return new XmlaExtraImpl();
106    }
107
108    /**
109     * Returns a new OlapConnection opened with the credentials specified in the
110     * XMLA request or an existing connection if one can be found associated
111     * with the request session id.
112     *
113     * @param request Request
114     * @param propMap Extra properties
115     */
116    public OlapConnection getConnection(
117        XmlaRequest request,
118        Map<String, String> propMap)
119    {
120        String sessionId = request.getSessionId();
121        if (sessionId == null) {
122            // With a Simba O2X Client session ID is only null when
123            // serving "discover datasources".
124            //
125            // Let's have a magic ID for the non-authenticated session.
126            //
127            // REVIEW: Security hole?
128            sessionId = "<no_session>";
129        }
130        LOGGER.debug(
131            "Creating new connection for user [" + request.getUsername()
132            + "] and session [" + sessionId + "]");
133
134        Properties props = new Properties();
135        for (Map.Entry<String, String> entry : propMap.entrySet()) {
136            props.put(entry.getKey(), entry.getValue());
137        }
138        if (request.getUsername() != null) {
139            props.put(JDBC_USER, request.getUsername());
140        }
141        if (request.getPassword() != null) {
142            props.put(JDBC_PASSWORD, request.getPassword());
143        }
144
145        final String databaseName =
146           request
147               .getProperties()
148                   .get(PropertyDefinition.DataSourceInfo.name());
149
150        String catalogName =
151            request
152                .getProperties()
153                    .get(PropertyDefinition.Catalog.name());
154
155        if (catalogName == null
156            && request.getMethod() == Method.DISCOVER
157            && request.getRestrictions().containsKey(
158                Property.StandardMemberProperty
159                    .CATALOG_NAME.name()))
160        {
161            Object restriction =
162                request.getRestrictions().get(
163                    Property.StandardMemberProperty
164                        .CATALOG_NAME.name());
165            if (restriction instanceof List) {
166                final List requiredValues = (List) restriction;
167                catalogName =
168                    String.valueOf(
169                        requiredValues.get(0));
170            } else {
171                throw Util.newInternal(
172                    "unexpected restriction type: " + restriction.getClass());
173            }
174        }
175
176        return
177            getConnection(
178                databaseName,
179                catalogName,
180                request.getRoleName(),
181                props);
182    }
183
184    private enum SetType {
185        ROW_SET,
186        MD_DATA_SET
187    }
188
189    private static final String EMPTY_ROW_SET_XML_SCHEMA =
190        computeEmptyXsd(SetType.ROW_SET);
191
192    private static final String MD_DATA_SET_XML_SCHEMA =
193        computeXsd(SetType.MD_DATA_SET);
194
195    private static final String EMPTY_MD_DATA_SET_XML_SCHEMA =
196        computeEmptyXsd(SetType.MD_DATA_SET);
197
198    private static final String NS_XML_SQL =
199        "urn:schemas-microsoft-com:xml-sql";
200
201    //
202    // Some xml schema data types.
203    //
204    public static final String XSD_BOOLEAN = "xsd:boolean";
205    public static final String XSD_STRING = "xsd:string";
206    public static final String XSD_UNSIGNED_INT = "xsd:unsignedInt";
207
208    public static final String XSD_BYTE = "xsd:byte";
209    public static final byte XSD_BYTE_MAX_INCLUSIVE = 127;
210    public static final byte XSD_BYTE_MIN_INCLUSIVE = -128;
211
212    public static final String XSD_SHORT = "xsd:short";
213    public static final short XSD_SHORT_MAX_INCLUSIVE = 32767;
214    public static final short XSD_SHORT_MIN_INCLUSIVE = -32768;
215
216    public static final String XSD_INT = "xsd:int";
217    public static final int XSD_INT_MAX_INCLUSIVE = 2147483647;
218    public static final int XSD_INT_MIN_INCLUSIVE = -2147483648;
219
220    public static final String XSD_LONG = "xsd:long";
221    public static final long XSD_LONG_MAX_INCLUSIVE = 9223372036854775807L;
222    public static final long XSD_LONG_MIN_INCLUSIVE = -9223372036854775808L;
223
224    // xsd:double: IEEE 64-bit floating-point
225    public static final String XSD_DOUBLE = "xsd:double";
226
227    public static final String XSD_FLOAT = "xsd:float";
228
229    // xsd:decimal: Decimal numbers (BigDecimal)
230    public static final String XSD_DECIMAL = "xsd:decimal";
231
232    // xsd:integer: Signed integers of arbitrary length (BigInteger)
233    public static final String XSD_INTEGER = "xsd:integer";
234
235    public static boolean isValidXsdInt(long l) {
236        return (l <= XSD_INT_MAX_INCLUSIVE) && (l >= XSD_INT_MIN_INCLUSIVE);
237    }
238
239    /**
240     * Takes a DataType String (null, Integer, Numeric or non-null)
241     * and Value Object (Integer, Double, String, other) and
242     * canonicalizes them to XSD data type and corresponding object.
243     * <p>
244     * If the input DataType is Integer, then it attempts to return
245     * an XSD_INT with value java.lang.Integer (and failing that an
246     * XSD_LONG (java.lang.Long) or XSD_INTEGER (java.math.BigInteger)).
247     * Worst case is the value loses precision with any integral
248     * representation and must be returned as a decimal type (Double
249     * or java.math.BigDecimal).
250     * <p>
251     * If the input DataType is Decimal, then it attempts to return
252     * an XSD_DOUBLE with value java.lang.Double (and failing that an
253     * XSD_DECIMAL (java.math.BigDecimal)).
254     */
255    static class ValueInfo {
256
257        /**
258         * Returns XSD_INT, XSD_DOUBLE, XSD_STRING or null.
259         *
260         * @param dataType null, Integer, Numeric or non-null.
261         * @return Returns the suggested XSD type for a given datatype
262         */
263        static String getValueTypeHint(final String dataType) {
264            if (dataType != null) {
265                return (dataType.equals("Integer"))
266                    ? XSD_INT
267                    : ((dataType.equals("Numeric"))
268                        ? XSD_DOUBLE
269                        : XSD_STRING);
270            } else {
271                return null;
272            }
273        }
274
275        String valueType;
276        Object value;
277        boolean isDecimal;
278
279        ValueInfo(final String dataType, final Object inputValue) {
280            final String valueTypeHint = getValueTypeHint(dataType);
281
282            // This is a hint: should it be a string, integer or decimal type.
283            // In the following, if the hint is integer, then there is
284            // an attempt that the value types
285            // be XSD_INT, XST_LONG, or XSD_INTEGER (but they could turn
286            // out to be XSD_DOUBLE or XSD_DECIMAL if precision is loss
287            // with the integral formats). It the hint is a decimal type
288            // (double, float, decimal), then a XSD_DOUBLE or XSD_DECIMAL
289            // is returned.
290            if (valueTypeHint != null) {
291                // The value type is a hint. If the value can be
292                // converted to the data type without precision loss, ok;
293                // otherwise value data type must be adjusted.
294
295                if (valueTypeHint.equals(XSD_STRING)) {
296                    // For String types, nothing to do.
297                    this.valueType = valueTypeHint;
298                    this.value = inputValue;
299                    this.isDecimal = false;
300
301                } else if (valueTypeHint.equals(XSD_INT)) {
302                    // If valueTypeHint is XSD_INT, then see if value can be
303                    // converted to (first choice) integer, (second choice),
304                    // long and (last choice) BigInteger - otherwise must
305                    // use double/decimal.
306
307                    // Most of the time value ought to be an Integer so
308                    // try it first
309                    if (inputValue instanceof Integer) {
310                        // For integer, its already the right type
311                        this.valueType = valueTypeHint;
312                        this.value = inputValue;
313                        this.isDecimal = false;
314
315                    } else if (inputValue instanceof Byte) {
316                        this.valueType = XSD_BYTE;
317                        this.value = inputValue;
318                        this.isDecimal = false;
319
320                    } else if (inputValue instanceof Short) {
321                        this.valueType = XSD_SHORT;
322                        this.value = inputValue;
323                        this.isDecimal = false;
324
325                    } else if (inputValue instanceof Long) {
326                        // See if it can be an integer or long
327                        long lval = (Long) inputValue;
328                        setValueAndType(lval);
329
330                    } else if (inputValue instanceof BigInteger) {
331                        BigInteger bi = (BigInteger) inputValue;
332                        // See if it can be an integer or long
333                        long lval = bi.longValue();
334                        if (bi.equals(BigInteger.valueOf(lval))) {
335                            // It can be converted from BigInteger to long
336                            // without loss of precision.
337                            setValueAndType(lval);
338                        } else {
339                            // It can not be converted to a long.
340                            this.valueType = XSD_INTEGER;
341                            this.value = inputValue;
342                            this.isDecimal = false;
343                        }
344
345                    } else if (inputValue instanceof Float) {
346                        Float f = (Float) inputValue;
347                        // See if it can be an integer or long
348                        long lval = f.longValue();
349                        if (f.equals(new Float(lval))) {
350                            // It can be converted from double to long
351                            // without loss of precision.
352                            setValueAndType(lval);
353
354                        } else {
355                            // It can not be converted to a long.
356                            this.valueType = XSD_FLOAT;
357                            this.value = inputValue;
358                            this.isDecimal = true;
359                        }
360
361                    } else if (inputValue instanceof Double) {
362                        Double d = (Double) inputValue;
363                        // See if it can be an integer or long
364                        long lval = d.longValue();
365                        if (d.equals(new Double(lval))) {
366                            // It can be converted from double to long
367                            // without loss of precision.
368                            setValueAndType(lval);
369
370                        } else {
371                            // It can not be converted to a long.
372                            this.valueType = XSD_DOUBLE;
373                            this.value = inputValue;
374                            this.isDecimal = true;
375                        }
376
377                    } else if (inputValue instanceof BigDecimal) {
378                        // See if it can be an integer or long
379                        BigDecimal bd = (BigDecimal) inputValue;
380                        try {
381                            // Can it be converted to a long
382                            // Throws ArithmeticException on conversion failure.
383                            // The following line is only available in
384                            // Java5 and above:
385                            //long lval = bd.longValueExact();
386                            long lval = bd.longValue();
387
388                            setValueAndType(lval);
389                        } catch (ArithmeticException ex) {
390                            // No, it can not be converted to long
391
392                            try {
393                                // Can it be an integer
394                                BigInteger bi = bd.toBigIntegerExact();
395                                this.valueType = XSD_INTEGER;
396                                this.value = bi;
397                                this.isDecimal = false;
398                            } catch (ArithmeticException ex1) {
399                                // OK, its a decimal
400                                this.valueType = XSD_DECIMAL;
401                                this.value = inputValue;
402                                this.isDecimal = true;
403                            }
404                        }
405
406                    } else if (inputValue instanceof Number) {
407                        // Don't know what Number type we have here.
408                        // Note: this could result in precision loss.
409                        this.value = ((Number) inputValue).longValue();
410                        this.valueType = valueTypeHint;
411                        this.isDecimal = false;
412
413                    } else {
414                        // Who knows what we are dealing with,
415                        // hope for the best?!?
416                        this.valueType = valueTypeHint;
417                        this.value = inputValue;
418                        this.isDecimal = false;
419                    }
420
421                } else if (valueTypeHint.equals(XSD_DOUBLE)) {
422                    // The desired type is double.
423
424                    // Most of the time value ought to be an Double so
425                    // try it first
426                    if (inputValue instanceof Double) {
427                        // For Double, its already the right type
428                        this.valueType = valueTypeHint;
429                        this.value = inputValue;
430                        this.isDecimal = true;
431
432                    } else if (inputValue instanceof Byte
433                        || inputValue instanceof Short
434                        || inputValue instanceof Integer
435                        || inputValue instanceof Long)
436                    {
437                        // Convert from byte/short/integer/long to double
438                        this.value = ((Number) inputValue).doubleValue();
439                        this.valueType = valueTypeHint;
440                        this.isDecimal = true;
441
442                    } else if (inputValue instanceof Float) {
443                        this.value = inputValue;
444                        this.valueType = XSD_FLOAT;
445                        this.isDecimal = true;
446
447                    } else if (inputValue instanceof BigDecimal) {
448                        BigDecimal bd = (BigDecimal) inputValue;
449                        double dval = bd.doubleValue();
450                        // make with same scale as Double
451                        try {
452                            BigDecimal bd2 =
453                                Util.makeBigDecimalFromDouble(dval);
454                            // Can it be a double
455                            // Must use compareTo - see BigDecimal.equals
456                            if (bd.compareTo(bd2) == 0) {
457                                this.valueType = XSD_DOUBLE;
458                                this.value = dval;
459                            } else {
460                                this.valueType = XSD_DECIMAL;
461                                this.value = inputValue;
462                            }
463                        } catch (NumberFormatException ex) {
464                            this.valueType = XSD_DECIMAL;
465                            this.value = inputValue;
466                        }
467                        this.isDecimal = true;
468
469                    } else if (inputValue instanceof BigInteger) {
470                        // What should be done here? Convert ot BigDecimal
471                        // and see if it can be a double or not?
472                        // See if there is loss of precision in the convertion?
473                        // Don't know. For now, just keep it a integral
474                        // value.
475                        BigInteger bi = (BigInteger) inputValue;
476                        // See if it can be an integer or long
477                        long lval = bi.longValue();
478                        if (bi.equals(BigInteger.valueOf(lval))) {
479                            // It can be converted from BigInteger to long
480                            // without loss of precision.
481                            setValueAndType(lval);
482                        } else {
483                            // It can not be converted to a long.
484                            this.valueType = XSD_INTEGER;
485                            this.value = inputValue;
486                            this.isDecimal = true;
487                        }
488
489                    } else if (inputValue instanceof Number) {
490                        // Don't know what Number type we have here.
491                        // Note: this could result in precision loss.
492                        this.value = ((Number) inputValue).doubleValue();
493                        this.valueType = valueTypeHint;
494                        this.isDecimal = true;
495
496                    } else {
497                        // Who knows what we are dealing with,
498                        // hope for the best?!?
499                        this.valueType = valueTypeHint;
500                        this.value = inputValue;
501                        this.isDecimal = true;
502                    }
503                }
504            } else {
505                // There is no valueType "hint", so just get it from the value.
506                if (inputValue instanceof String) {
507                    this.valueType = XSD_STRING;
508                    this.value = inputValue;
509                    this.isDecimal = false;
510
511                } else if (inputValue instanceof Integer) {
512                    this.valueType = XSD_INT;
513                    this.value = inputValue;
514                    this.isDecimal = false;
515
516                } else if (inputValue instanceof Byte) {
517                    Byte b = (Byte) inputValue;
518                    this.valueType = XSD_BYTE;
519                    this.value = b.intValue();
520                    this.isDecimal = false;
521
522                } else if (inputValue instanceof Short) {
523                    Short s = (Short) inputValue;
524                    this.valueType = XSD_SHORT;
525                    this.value = s.intValue();
526                    this.isDecimal = false;
527
528                } else if (inputValue instanceof Long) {
529                    // See if it can be an integer or long
530                    setValueAndType((Long) inputValue);
531
532                } else if (inputValue instanceof BigInteger) {
533                    BigInteger bi = (BigInteger) inputValue;
534                    // See if it can be an integer or long
535                    long lval = bi.longValue();
536                    if (bi.equals(BigInteger.valueOf(lval))) {
537                        // It can be converted from BigInteger to long
538                        // without loss of precision.
539                        setValueAndType(lval);
540                    } else {
541                        // It can not be converted to a long.
542                        this.valueType = XSD_INTEGER;
543                        this.value = inputValue;
544                        this.isDecimal = false;
545                    }
546
547                } else if (inputValue instanceof Float) {
548                    this.valueType = XSD_FLOAT;
549                    this.value = inputValue;
550                    this.isDecimal = true;
551
552                } else if (inputValue instanceof Double) {
553                    this.valueType = XSD_DOUBLE;
554                    this.value = inputValue;
555                    this.isDecimal = true;
556
557                } else if (inputValue instanceof BigDecimal) {
558                    // See if it can be a double
559                    BigDecimal bd = (BigDecimal) inputValue;
560                    double dval = bd.doubleValue();
561                    // make with same scale as Double
562                    try {
563                        BigDecimal bd2 =
564                                Util.makeBigDecimalFromDouble(dval);
565                        // Can it be a double
566                        // Must use compareTo - see BigDecimal.equals
567                        if (bd.compareTo(bd2) == 0) {
568                            this.valueType = XSD_DOUBLE;
569                            this.value = dval;
570                        } else {
571                            this.valueType = XSD_DECIMAL;
572                            this.value = inputValue;
573                        }
574                    } catch (NumberFormatException ex) {
575                        this.valueType = XSD_DECIMAL;
576                        this.value = inputValue;
577                    }
578                    this.isDecimal = true;
579
580                } else if (inputValue instanceof Number) {
581                    // Don't know what Number type we have here.
582                    // Note: this could result in precision loss.
583                    this.value = ((Number) inputValue).longValue();
584                    this.valueType = XSD_LONG;
585                    this.isDecimal = false;
586
587                } else if (inputValue instanceof Boolean) {
588                    this.value = inputValue;
589                    this.valueType = XSD_BOOLEAN;
590                    this.isDecimal = false;
591                } else {
592                    // Who knows what we are dealing with,
593                    // hope for the best?!?
594                    this.valueType = XSD_STRING;
595                    this.value = inputValue;
596                    this.isDecimal = false;
597                }
598            }
599        }
600        private void setValueAndType(long lval) {
601            if (! isValidXsdInt(lval)) {
602                // No, it can not be a integer, must be a long
603                this.valueType = XSD_LONG;
604                this.value = lval;
605            } else {
606                // Its an integer.
607                this.valueType = XSD_INT;
608                this.value = (int) lval;
609            }
610            this.isDecimal = false;
611        }
612    }
613
614
615
616    private static String computeXsd(SetType setType) {
617        final StringWriter sw = new StringWriter();
618        SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3);
619        writeDatasetXmlSchema(writer, setType);
620        writer.flush();
621        return sw.toString();
622    }
623
624    private static String computeEmptyXsd(SetType setType) {
625        final StringWriter sw = new StringWriter();
626        SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3);
627        writeEmptyDatasetXmlSchema(writer, setType);
628        writer.flush();
629        return sw.toString();
630    }
631
632    private static interface QueryResult {
633        void unparse(SaxWriter res) throws SAXException, OlapException;
634        void close() throws SQLException;
635        void metadata(SaxWriter writer);
636    }
637
638    /**
639     * Creates an <code>XmlaHandler</code>.
640     *
641     * <p>The connection factory may be null, as long as you override
642     * {@link #getConnection(String, String, String, Properties)}.
643     *
644     * @param connectionFactory Connection factory
645     * @param prefix XML Namespace. Typical value is "xmla", but a value of
646     *   "cxmla" works around an Internet Explorer 7 bug
647     */
648    public XmlaHandler(ConnectionFactory connectionFactory, String prefix)
649    {
650        assert prefix != null;
651        this.connectionFactory = connectionFactory;
652        this.prefix = prefix;
653    }
654
655    /**
656     * Processes a request.
657     *
658     * @param request  XML request, for example, "<SOAP-ENV:Envelope ...>".
659     * @param response Destination for response
660     * @throws XmlaException on error
661     */
662    public void process(XmlaRequest request, XmlaResponse response)
663        throws XmlaException
664    {
665        Method method = request.getMethod();
666        long start = System.currentTimeMillis();
667
668        switch (method) {
669        case DISCOVER:
670            discover(request, response);
671            break;
672        case EXECUTE:
673            execute(request, response);
674            break;
675        default:
676            throw new XmlaException(
677                CLIENT_FAULT_FC,
678                HSB_BAD_METHOD_CODE,
679                HSB_BAD_METHOD_FAULT_FS,
680                new IllegalArgumentException(
681                    "Unsupported XML/A method: " + method));
682        }
683        if (LOGGER.isDebugEnabled()) {
684            long end = System.currentTimeMillis();
685            LOGGER.debug("XmlaHandler.process: time = " + (end - start));
686            LOGGER.debug("XmlaHandler.process: " + Util.printMemory());
687        }
688    }
689
690    private void checkFormat(XmlaRequest request) throws XmlaException {
691        // Check response's rowset format in request
692        final Map<String, String> properties = request.getProperties();
693        if (request.isDrillThrough()) {
694            Format format = getFormat(request, null);
695            if (format != Format.Tabular) {
696                throw new XmlaException(
697                    CLIENT_FAULT_FC,
698                    HSB_DRILL_THROUGH_FORMAT_CODE,
699                    HSB_DRILL_THROUGH_FORMAT_FAULT_FS,
700                    new UnsupportedOperationException(
701                        "<Format>: only 'Tabular' allowed when drilling "
702                        + "through"));
703            }
704        } else {
705            final String formatName =
706                properties.get(PropertyDefinition.Format.name());
707            if (formatName != null) {
708                Format format = getFormat(request, null);
709                if (format != Format.Multidimensional
710                    && format != Format.Tabular)
711                {
712                    throw new UnsupportedOperationException(
713                        "<Format>: only 'Multidimensional', 'Tabular' "
714                        + "currently supported");
715                }
716            }
717            final String axisFormatName =
718                properties.get(PropertyDefinition.AxisFormat.name());
719            if (axisFormatName != null) {
720                AxisFormat axisFormat = Util.lookup(
721                    AxisFormat.class, axisFormatName, null);
722
723                if (axisFormat != AxisFormat.TupleFormat) {
724                    throw new UnsupportedOperationException(
725                        "<AxisFormat>: only 'TupleFormat' currently supported");
726                }
727            }
728        }
729    }
730
731    private void execute(
732        XmlaRequest request,
733        XmlaResponse response)
734        throws XmlaException
735    {
736        final Map<String, String> properties = request.getProperties();
737
738        // Default responseMimeType is SOAP.
739        Enumeration.ResponseMimeType responseMimeType =
740            getResponseMimeType(request);
741
742        // Default value is SchemaData, or Data for JSON responses.
743        final String contentName =
744            properties.get(PropertyDefinition.Content.name());
745        Content content = Util.lookup(
746            Content.class,
747            contentName,
748            responseMimeType == Enumeration.ResponseMimeType.JSON
749                ? Content.Data
750                : Content.DEFAULT);
751
752        // Handle execute
753        QueryResult result = null;
754        try {
755            if (request.isDrillThrough()) {
756                result = executeDrillThroughQuery(request);
757            } else {
758                result = executeQuery(request);
759            }
760
761            SaxWriter writer = response.getWriter();
762            writer.startDocument();
763
764            writer.startElement(
765                prefix + ":ExecuteResponse",
766                "xmlns:" + prefix, NS_XMLA);
767            writer.startElement(prefix + ":return");
768            boolean rowset =
769                request.isDrillThrough()
770                || Format.Tabular.name().equals(
771                    request.getProperties().get(
772                        PropertyDefinition.Format.name()));
773            writer.startElement(
774                "root",
775                "xmlns",
776                result == null
777                    ? NS_XMLA_EMPTY
778                    : rowset
779                        ? NS_XMLA_ROWSET
780                        : NS_XMLA_MDDATASET,
781                "xmlns:xsi", NS_XSI,
782                "xmlns:xsd", NS_XSD,
783                "xmlns:EX", NS_XMLA_EX);
784
785            switch (content) {
786            case Schema:
787            case SchemaData:
788                if (result != null) {
789                    result.metadata(writer);
790                } else {
791                    if (rowset) {
792                        writer.verbatim(EMPTY_ROW_SET_XML_SCHEMA);
793                    } else {
794                        writer.verbatim(EMPTY_MD_DATA_SET_XML_SCHEMA);
795                    }
796                }
797                break;
798            }
799
800            try {
801                switch (content) {
802                case Data:
803                case SchemaData:
804                case DataOmitDefaultSlicer:
805                case DataIncludeDefaultSlicer:
806                    if (result != null) {
807                        result.unparse(writer);
808                    }
809                    break;
810                }
811            } catch (XmlaException xex) {
812                throw xex;
813            } catch (Throwable t) {
814                throw new XmlaException(
815                    SERVER_FAULT_FC,
816                    HSB_EXECUTE_UNPARSE_CODE,
817                    HSB_EXECUTE_UNPARSE_FAULT_FS,
818                    t);
819            } finally {
820                writer.endElement(); // root
821                writer.endElement(); // return
822                writer.endElement(); // ExecuteResponse
823            }
824            writer.endDocument();
825        } finally {
826            if (result != null) {
827                try {
828                    result.close();
829                } catch (SQLException e) {
830                    // ignore
831                }
832            }
833        }
834    }
835
836    /**
837     * Computes the XML Schema for a dataset.
838     *
839     * @param writer SAX writer
840     * @param settype rowset or dataset?
841     * @see RowsetDefinition#writeRowsetXmlSchema(SaxWriter)
842     */
843    static void writeDatasetXmlSchema(SaxWriter writer, SetType settype) {
844        String setNsXmla =
845            (settype == SetType.ROW_SET)
846            ? NS_XMLA_ROWSET
847            : NS_XMLA_MDDATASET;
848
849        writer.startElement(
850            "xsd:schema",
851            "xmlns:xsd", NS_XSD,
852            "targetNamespace", setNsXmla,
853            "xmlns", setNsXmla,
854            "xmlns:xsi", NS_XSI,
855            "xmlns:sql", NS_XML_SQL,
856            "elementFormDefault", "qualified");
857
858        // MemberType
859
860        writer.startElement(
861            "xsd:complexType",
862            "name", "MemberType");
863        writer.startElement("xsd:sequence");
864        writer.element(
865            "xsd:element",
866            "name", "UName",
867            "type", XSD_STRING);
868        writer.element(
869            "xsd:element",
870            "name", "Caption",
871            "type", XSD_STRING);
872        writer.element(
873            "xsd:element",
874            "name", "LName",
875            "type", XSD_STRING);
876        writer.element(
877            "xsd:element",
878            "name", "LNum",
879            "type", XSD_UNSIGNED_INT);
880        writer.element(
881            "xsd:element",
882            "name", "DisplayInfo",
883            "type", XSD_UNSIGNED_INT);
884        writer.startElement(
885            "xsd:sequence",
886            "maxOccurs", "unbounded",
887            "minOccurs", 0);
888        writer.element(
889            "xsd:any",
890            "processContents", "lax",
891            "maxOccurs", "unbounded");
892        writer.endElement(); // xsd:sequence
893        writer.endElement(); // xsd:sequence
894        writer.element(
895            "xsd:attribute",
896                "name", "Hierarchy",
897                "type", XSD_STRING);
898        writer.endElement(); // xsd:complexType name="MemberType"
899
900        // PropType
901
902        writer.startElement(
903            "xsd:complexType",
904            "name", "PropType");
905        writer.element(
906            "xsd:attribute",
907            "name", "name",
908            "type", XSD_STRING);
909        writer.endElement(); // xsd:complexType name="PropType"
910
911        // TupleType
912
913        writer.startElement(
914            "xsd:complexType",
915            "name", "TupleType");
916        writer.startElement(
917            "xsd:sequence",
918            "maxOccurs", "unbounded");
919        writer.element(
920            "xsd:element",
921            "name", "Member",
922            "type", "MemberType");
923        writer.endElement(); // xsd:sequence
924        writer.endElement(); // xsd:complexType name="TupleType"
925
926        // MembersType
927
928        writer.startElement(
929            "xsd:complexType",
930            "name", "MembersType");
931        writer.startElement(
932            "xsd:sequence",
933            "maxOccurs", "unbounded");
934        writer.element(
935            "xsd:element",
936            "name", "Member",
937            "type", "MemberType");
938        writer.endElement(); // xsd:sequence
939        writer.element(
940            "xsd:attribute",
941            "name", "Hierarchy",
942            "type", XSD_STRING);
943        writer.endElement(); // xsd:complexType
944
945        // TuplesType
946
947        writer.startElement(
948            "xsd:complexType",
949            "name", "TuplesType");
950        writer.startElement(
951            "xsd:sequence",
952            "maxOccurs", "unbounded");
953        writer.element(
954            "xsd:element",
955            "name", "Tuple",
956            "type", "TupleType");
957        writer.endElement(); // xsd:sequence
958        writer.endElement(); // xsd:complexType
959
960        // CrossProductType
961
962        writer.startElement(
963            "xsd:complexType",
964            "name", "CrossProductType");
965        writer.startElement("xsd:sequence");
966        writer.startElement(
967            "xsd:choice",
968            "minOccurs", 0,
969            "maxOccurs", "unbounded");
970        writer.element(
971            "xsd:element",
972            "name", "Members",
973            "type", "MembersType");
974        writer.element(
975            "xsd:element",
976            "name", "Tuples",
977            "type", "TuplesType");
978        writer.endElement(); // xsd:choice
979        writer.endElement(); // xsd:sequence
980        writer.element(
981            "xsd:attribute",
982            "name", "Size",
983            "type", XSD_UNSIGNED_INT);
984        writer.endElement(); // xsd:complexType
985
986        // OlapInfo
987
988        writer.startElement(
989            "xsd:complexType",
990            "name", "OlapInfo");
991        writer.startElement("xsd:sequence");
992
993        { // <CubeInfo>
994            writer.startElement(
995                "xsd:element",
996                "name", "CubeInfo");
997            writer.startElement("xsd:complexType");
998            writer.startElement("xsd:sequence");
999
1000            { // <Cube>
1001                writer.startElement(
1002                    "xsd:element",
1003                    "name", "Cube",
1004                    "maxOccurs", "unbounded");
1005                writer.startElement("xsd:complexType");
1006                writer.startElement("xsd:sequence");
1007
1008                writer.element(
1009                    "xsd:element",
1010                    "name", "CubeName",
1011                    "type", XSD_STRING);
1012
1013                writer.endElement(); // xsd:sequence
1014                writer.endElement(); // xsd:complexType
1015                writer.endElement(); // xsd:element name=Cube
1016            }
1017
1018            writer.endElement(); // xsd:sequence
1019            writer.endElement(); // xsd:complexType
1020            writer.endElement(); // xsd:element name=CubeInfo
1021        }
1022
1023        { // <AxesInfo>
1024            writer.startElement(
1025                "xsd:element",
1026                "name", "AxesInfo");
1027            writer.startElement("xsd:complexType");
1028            writer.startElement("xsd:sequence");
1029            { // <AxisInfo>
1030                writer.startElement(
1031                    "xsd:element",
1032                    "name", "AxisInfo",
1033                    "maxOccurs", "unbounded");
1034                writer.startElement("xsd:complexType");
1035                writer.startElement("xsd:sequence");
1036
1037                { // <HierarchyInfo>
1038                    writer.startElement(
1039                        "xsd:element",
1040                        "name", "HierarchyInfo",
1041                        "minOccurs", 0,
1042                        "maxOccurs", "unbounded");
1043                    writer.startElement("xsd:complexType");
1044                    writer.startElement("xsd:sequence");
1045                    writer.startElement(
1046                        "xsd:sequence",
1047                        "maxOccurs", "unbounded");
1048                    writer.element(
1049                        "xsd:element",
1050                        "name", "UName",
1051                        "type", "PropType");
1052                    writer.element(
1053                        "xsd:element",
1054                        "name", "Caption",
1055                        "type", "PropType");
1056                    writer.element(
1057                        "xsd:element",
1058                        "name", "LName",
1059                        "type", "PropType");
1060                    writer.element(
1061                        "xsd:element",
1062                        "name", "LNum",
1063                        "type", "PropType");
1064                    writer.element(
1065                        "xsd:element",
1066                        "name", "DisplayInfo",
1067                        "type", "PropType",
1068                        "minOccurs", 0,
1069                        "maxOccurs", "unbounded");
1070                    if (false)
1071                    writer.element(
1072                        "xsd:element",
1073                        "name", "PARENT_MEMBER_NAME",
1074                        "type", "PropType",
1075                        "minOccurs", 0,
1076                        "maxOccurs", "unbounded");
1077                    writer.endElement(); // xsd:sequence
1078
1079                    // This is the Depth element for JPivot??
1080                    writer.startElement("xsd:sequence");
1081                    writer.element(
1082                        "xsd:any",
1083                        "processContents", "lax",
1084                        "minOccurs", 0,
1085                        "maxOccurs", "unbounded");
1086                    writer.endElement(); // xsd:sequence
1087
1088                    writer.endElement(); // xsd:sequence
1089                    writer.element(
1090                        "xsd:attribute",
1091                        "name", "name",
1092                        "type", XSD_STRING,
1093                        "use", "required");
1094                    writer.endElement(); // xsd:complexType
1095                    writer.endElement(); // xsd:element name=HierarchyInfo
1096                }
1097                writer.endElement(); // xsd:sequence
1098                writer.element(
1099                    "xsd:attribute",
1100                    "name", "name",
1101                    "type", XSD_STRING);
1102                writer.endElement(); // xsd:complexType
1103                writer.endElement(); // xsd:element name=AxisInfo
1104            }
1105            writer.endElement(); // xsd:sequence
1106            writer.endElement(); // xsd:complexType
1107            writer.endElement(); // xsd:element name=AxesInfo
1108        }
1109
1110        // CellInfo
1111
1112        { // <CellInfo>
1113            writer.startElement(
1114                "xsd:element",
1115                "name", "CellInfo");
1116            writer.startElement("xsd:complexType");
1117            writer.startElement("xsd:sequence");
1118            writer.startElement(
1119                "xsd:sequence",
1120                "minOccurs", 0,
1121                "maxOccurs", "unbounded");
1122            writer.startElement("xsd:choice");
1123            writer.element(
1124                "xsd:element",
1125                "name", "Value",
1126                "type", "PropType");
1127            writer.element(
1128                "xsd:element",
1129                "name", "FmtValue",
1130                "type", "PropType");
1131            writer.element(
1132                "xsd:element",
1133                "name", "BackColor",
1134                "type", "PropType");
1135            writer.element(
1136                "xsd:element",
1137                "name", "ForeColor",
1138                "type", "PropType");
1139            writer.element(
1140                "xsd:element",
1141                "name", "FontName",
1142                "type", "PropType");
1143            writer.element(
1144                "xsd:element",
1145                "name", "FontSize",
1146                "type", "PropType");
1147            writer.element(
1148                "xsd:element",
1149                "name", "FontFlags",
1150                "type", "PropType");
1151            writer.element(
1152                "xsd:element",
1153                "name", "FormatString",
1154                "type", "PropType");
1155            writer.element(
1156                "xsd:element",
1157                "name", "NonEmptyBehavior",
1158                "type", "PropType");
1159            writer.element(
1160                "xsd:element",
1161                "name", "SolveOrder",
1162                "type", "PropType");
1163            writer.element(
1164                "xsd:element",
1165                "name", "Updateable",
1166                "type", "PropType");
1167            writer.element(
1168                "xsd:element",
1169                "name", "Visible",
1170                "type", "PropType");
1171            writer.element(
1172                "xsd:element",
1173                "name", "Expression",
1174                "type", "PropType");
1175            writer.endElement(); // xsd:choice
1176            writer.endElement(); // xsd:sequence
1177            writer.startElement(
1178                "xsd:sequence",
1179                "maxOccurs", "unbounded",
1180                "minOccurs", 0);
1181            writer.element(
1182                "xsd:any",
1183                "processContents", "lax",
1184                "maxOccurs", "unbounded");
1185            writer.endElement(); // xsd:sequence
1186            writer.endElement(); // xsd:sequence
1187            writer.endElement(); // xsd:complexType
1188            writer.endElement(); // xsd:element name=CellInfo
1189        }
1190
1191        writer.endElement(); // xsd:sequence
1192        writer.endElement(); // xsd:complexType
1193
1194        // Axes
1195
1196        writer.startElement(
1197            "xsd:complexType",
1198            "name", "Axes");
1199        writer.startElement(
1200            "xsd:sequence",
1201            "maxOccurs", "unbounded");
1202        { // <Axis>
1203            writer.startElement(
1204                "xsd:element",
1205                "name", "Axis");
1206            writer.startElement("xsd:complexType");
1207            writer.startElement(
1208                "xsd:choice",
1209                "minOccurs", 0,
1210                "maxOccurs", "unbounded");
1211            writer.element(
1212                "xsd:element",
1213                "name", "CrossProduct",
1214                "type", "CrossProductType");
1215            writer.element(
1216                "xsd:element",
1217                "name", "Tuples",
1218                "type", "TuplesType");
1219            writer.element(
1220                "xsd:element",
1221                "name", "Members",
1222                "type", "MembersType");
1223            writer.endElement(); // xsd:choice
1224            writer.element(
1225                "xsd:attribute",
1226                "name", "name",
1227                "type", XSD_STRING);
1228            writer.endElement(); // xsd:complexType
1229        }
1230        writer.endElement(); // xsd:element
1231        writer.endElement(); // xsd:sequence
1232        writer.endElement(); // xsd:complexType
1233
1234        // CellData
1235
1236        writer.startElement(
1237            "xsd:complexType",
1238            "name", "CellData");
1239        writer.startElement("xsd:sequence");
1240        { // <Cell>
1241            writer.startElement(
1242                "xsd:element",
1243                "name", "Cell",
1244                "minOccurs", 0,
1245                "maxOccurs", "unbounded");
1246            writer.startElement("xsd:complexType");
1247            writer.startElement(
1248                "xsd:sequence",
1249                "maxOccurs", "unbounded");
1250            writer.startElement("xsd:choice");
1251            writer.element(
1252                "xsd:element",
1253                "name", "Value");
1254            writer.element(
1255                "xsd:element",
1256                "name", "FmtValue",
1257                "type", XSD_STRING);
1258            writer.element(
1259                "xsd:element",
1260                "name", "BackColor",
1261                "type", XSD_UNSIGNED_INT);
1262            writer.element(
1263                "xsd:element",
1264                "name", "ForeColor",
1265                "type", XSD_UNSIGNED_INT);
1266            writer.element(
1267                "xsd:element",
1268                "name", "FontName",
1269                "type", XSD_STRING);
1270            writer.element(
1271                "xsd:element",
1272                "name", "FontSize",
1273                "type", "xsd:unsignedShort");
1274            writer.element(
1275                "xsd:element",
1276                "name", "FontFlags",
1277                "type", XSD_UNSIGNED_INT);
1278            writer.element(
1279                "xsd:element",
1280                "name", "FormatString",
1281                "type", XSD_STRING);
1282            writer.element(
1283                "xsd:element",
1284                "name", "NonEmptyBehavior",
1285                "type", "xsd:unsignedShort");
1286            writer.element(
1287                "xsd:element",
1288                "name", "SolveOrder",
1289                "type", XSD_UNSIGNED_INT);
1290            writer.element(
1291                "xsd:element",
1292                "name", "Updateable",
1293                "type", XSD_UNSIGNED_INT);
1294            writer.element(
1295                "xsd:element",
1296                "name", "Visible",
1297                "type", XSD_UNSIGNED_INT);
1298            writer.element(
1299                "xsd:element",
1300                "name", "Expression",
1301                "type", XSD_STRING);
1302            writer.endElement(); // xsd:choice
1303            writer.endElement(); // xsd:sequence
1304            writer.element(
1305                "xsd:attribute",
1306                "name", "CellOrdinal",
1307                "type", XSD_UNSIGNED_INT,
1308                "use", "required");
1309            writer.endElement(); // xsd:complexType
1310            writer.endElement(); // xsd:element name=Cell
1311        }
1312        writer.endElement(); // xsd:sequence
1313        writer.endElement(); // xsd:complexType
1314
1315        { // <root>
1316            writer.startElement(
1317                "xsd:element",
1318                "name", "root");
1319            writer.startElement("xsd:complexType");
1320            writer.startElement(
1321                "xsd:sequence",
1322                "maxOccurs", "unbounded");
1323            writer.element(
1324                "xsd:element",
1325                "name", "OlapInfo",
1326                "type", "OlapInfo");
1327            writer.element(
1328                "xsd:element",
1329                "name", "Axes",
1330                "type", "Axes");
1331            writer.element(
1332                "xsd:element",
1333                "name", "CellData",
1334                "type", "CellData");
1335            writer.endElement(); // xsd:sequence
1336            writer.endElement(); // xsd:complexType
1337            writer.endElement(); // xsd:element name=root
1338        }
1339
1340        writer.endElement(); // xsd:schema
1341    }
1342
1343    static void writeEmptyDatasetXmlSchema(SaxWriter writer, SetType setType) {
1344        String setNsXmla = NS_XMLA_ROWSET;
1345        writer.startElement(
1346            "xsd:schema",
1347            "xmlns:xsd", NS_XSD,
1348            "targetNamespace", setNsXmla,
1349            "xmlns", setNsXmla,
1350            "xmlns:xsi", NS_XSI,
1351            "xmlns:sql", NS_XML_SQL,
1352            "elementFormDefault", "qualified");
1353
1354        writer.element(
1355            "xsd:element",
1356            "name", "root");
1357
1358        writer.endElement(); // xsd:schema
1359    }
1360
1361    private QueryResult executeDrillThroughQuery(XmlaRequest request)
1362        throws XmlaException
1363    {
1364        checkFormat(request);
1365
1366        final Map<String, String> properties = request.getProperties();
1367        String tabFields =
1368            properties.get(PropertyDefinition.TableFields.name());
1369        if (tabFields != null && tabFields.length() == 0) {
1370            tabFields = null;
1371        }
1372        final String advancedFlag =
1373            properties.get(PropertyDefinition.AdvancedFlag.name());
1374        final boolean advanced = Boolean.parseBoolean(advancedFlag);
1375        final boolean enableRowCount =
1376            MondrianProperties.instance().EnableTotalCount.booleanValue();
1377        final int[] rowCountSlot = enableRowCount ? new int[]{0} : null;
1378        OlapConnection connection = null;
1379        OlapStatement statement = null;
1380        ResultSet resultSet = null;
1381        try {
1382            connection =
1383                getConnection(request, Collections.<String, String>emptyMap());
1384            statement = connection.createStatement();
1385            resultSet =
1386                getExtra(connection).executeDrillthrough(
1387                    statement,
1388                    request.getStatement(),
1389                    advanced,
1390                    tabFields,
1391                    rowCountSlot);
1392            int rowCount = enableRowCount ? rowCountSlot[0] : -1;
1393            return new TabularRowSet(resultSet, rowCount);
1394        } catch (XmlaException xex) {
1395            throw xex;
1396        } catch (SQLException sqle) {
1397            throw new XmlaException(
1398                SERVER_FAULT_FC,
1399                HSB_DRILL_THROUGH_SQL_CODE,
1400                HSB_DRILL_THROUGH_SQL_FAULT_FS,
1401                Util.newError(sqle, "Error in drill through"));
1402        } catch (RuntimeException e) {
1403            // NOTE: One important error is "cannot drill through on the cell"
1404            throw new XmlaException(
1405                SERVER_FAULT_FC,
1406                HSB_DRILL_THROUGH_SQL_CODE,
1407                HSB_DRILL_THROUGH_SQL_FAULT_FS,
1408                e);
1409        } finally {
1410            if (resultSet != null) {
1411                try {
1412                    resultSet.close();
1413                } catch (SQLException e) {
1414                    // ignore
1415                }
1416            }
1417            if (statement != null) {
1418                try {
1419                    statement.close();
1420                } catch (SQLException e) {
1421                    // ignore
1422                }
1423            }
1424            if (connection != null) {
1425                try {
1426                    connection.close();
1427                } catch (SQLException e) {
1428                    // ignore
1429                }
1430            }
1431        }
1432    }
1433
1434    static class Column {
1435        private final String name;
1436        private final String encodedName;
1437        private final String xsdType;
1438
1439        Column(String name, int type, int scale) {
1440            this.name = name;
1441
1442            // replace invalid XML element name, like " ", with "_x0020_" in
1443            // column headers, otherwise will generate a badly-formatted xml
1444            // doc.
1445            this.encodedName =
1446                XmlaUtil.ElementNameEncoder.INSTANCE.encode(name);
1447            this.xsdType = sqlToXsdType(type, scale);
1448        }
1449    }
1450
1451    static class TabularRowSet implements QueryResult {
1452        private final List<Column> columns = new ArrayList<Column>();
1453        private final List<Object[]> rows;
1454        private int totalCount;
1455
1456        /**
1457         * Creates a TabularRowSet based upon a SQL statement result.
1458         *
1459         * <p>Does not close the ResultSet, on success or failure. Client
1460         * must do it.
1461         *
1462         * @param rs Result set
1463         * @param totalCount Total number of rows. If >= 0, writes the
1464         *   "totalCount" attribute into the XMLA response.
1465         *
1466         * @throws SQLException on error
1467         */
1468        public TabularRowSet(
1469            ResultSet rs,
1470            int totalCount)
1471            throws SQLException
1472        {
1473            this.totalCount = totalCount;
1474            ResultSetMetaData md = rs.getMetaData();
1475            int columnCount = md.getColumnCount();
1476
1477            // populate column defs
1478            for (int i = 0; i < columnCount; i++) {
1479                columns.add(
1480                    new Column(
1481                        md.getColumnLabel(i + 1),
1482                        md.getColumnType(i + 1),
1483                        md.getScale(i + 1)));
1484            }
1485
1486            // Populate data; assume that SqlStatement is already positioned
1487            // on first row (or isDone() is true), and assume that the
1488            // number of rows returned is limited.
1489            rows = new ArrayList<Object[]>();
1490            while (rs.next()) {
1491                Object[] row = new Object[columnCount];
1492                for (int i = 0; i < columnCount; i++) {
1493                    row[i] = rs.getObject(i + 1);
1494                }
1495                rows.add(row);
1496            }
1497        }
1498
1499        /**
1500         * Alternate constructor for advanced drill-through.
1501         *
1502         * @param tableFieldMap Map from table name to a list of the names of
1503         *      the fields in the table
1504         * @param tableList List of table names
1505         */
1506        public TabularRowSet(
1507            Map<String, List<String>> tableFieldMap, List<String> tableList)
1508        {
1509            for (String tableName : tableList) {
1510                List<String> fieldNames = tableFieldMap.get(tableName);
1511                for (String fieldName : fieldNames) {
1512                    columns.add(
1513                        new Column(
1514                            tableName + "." + fieldName,
1515                            Types.VARCHAR, // don't know the real type
1516                            0));
1517                }
1518            }
1519
1520            rows = new ArrayList<Object[]>();
1521            Object[] row = new Object[columns.size()];
1522            for (int k = 0; k < row.length; k++) {
1523                row[k] = k;
1524            }
1525            rows.add(row);
1526        }
1527
1528        public void close() {
1529            // no resources to close
1530        }
1531
1532        public void unparse(SaxWriter writer) throws SAXException {
1533            // write total count row if enabled
1534            if (totalCount >= 0) {
1535                String countStr = Integer.toString(totalCount);
1536                writer.startElement("row");
1537                for (Column column : columns) {
1538                    writer.startElement(column.encodedName);
1539                    writer.characters(countStr);
1540                    writer.endElement();
1541                }
1542                writer.endElement(); // row
1543            }
1544
1545            for (Object[] row : rows) {
1546                writer.startElement("row");
1547                for (int i = 0; i < row.length; i++) {
1548                    writer.startElement(
1549                        columns.get(i).encodedName,
1550                        new Object[] {
1551                            "xsi:type",
1552                            columns.get(i).xsdType});
1553                    Object value = row[i];
1554                    if (value == null) {
1555                        writer.characters("null");
1556                    } else {
1557                        String valueString = value.toString();
1558                        if (value instanceof Number) {
1559                            valueString =
1560                                XmlaUtil.normalizeNumericString(valueString);
1561                        }
1562                        writer.characters(valueString);
1563                    }
1564                    writer.endElement();
1565                }
1566                writer.endElement(); // row
1567            }
1568        }
1569
1570        /**
1571         * Writes the tabular drillthrough schema
1572         *
1573         * @param writer Writer
1574         */
1575        public void metadata(SaxWriter writer) {
1576            writer.startElement(
1577                "xsd:schema",
1578                "xmlns:xsd", NS_XSD,
1579                "targetNamespace", NS_XMLA_ROWSET,
1580                "xmlns", NS_XMLA_ROWSET,
1581                "xmlns:xsi", NS_XSI,
1582                "xmlns:sql", NS_XML_SQL,
1583                "elementFormDefault", "qualified");
1584
1585            { // <root>
1586                writer.startElement(
1587                    "xsd:element",
1588                    "name", "root");
1589                writer.startElement("xsd:complexType");
1590                writer.startElement("xsd:sequence");
1591                writer.element(
1592                    "xsd:element",
1593                    "maxOccurs", "unbounded",
1594                    "minOccurs", 0,
1595                    "name", "row",
1596                    "type", "row");
1597                writer.endElement(); // xsd:sequence
1598                writer.endElement(); // xsd:complexType
1599                writer.endElement(); // xsd:element name=root
1600            }
1601
1602            { // xsd:simpleType name="uuid"
1603                writer.startElement(
1604                    "xsd:simpleType",
1605                        "name", "uuid");
1606                writer.startElement(
1607                    "xsd:restriction",
1608                    "base", XSD_STRING);
1609                writer.element(
1610                    "xsd:pattern",
1611                    "value", RowsetDefinition.UUID_PATTERN);
1612                writer.endElement(); // xsd:restriction
1613                writer.endElement(); // xsd:simpleType
1614            }
1615
1616            { // xsd:complexType name="row"
1617                writer.startElement(
1618                    "xsd:complexType",
1619                    "name", "row");
1620                writer.startElement("xsd:sequence");
1621                for (Column column : columns) {
1622                    writer.element(
1623                        "xsd:element",
1624                        "minOccurs", 0,
1625                        "name", column.encodedName,
1626                        "sql:field", column.name,
1627                        "type", column.xsdType);
1628                }
1629
1630                writer.endElement(); // xsd:sequence
1631                writer.endElement(); // xsd:complexType
1632            }
1633            writer.endElement(); // xsd:schema
1634        }
1635    }
1636
1637    /**
1638     * Converts a SQL type to XSD type.
1639     *
1640     * @param sqlType SQL type
1641     * @return XSD type
1642     */
1643    private static String sqlToXsdType(final int sqlType, final int scale) {
1644        switch (sqlType) {
1645        // Integer
1646        case Types.INTEGER:
1647        case Types.SMALLINT:
1648        case Types.TINYINT:
1649            return XSD_INT;
1650        case Types.NUMERIC:
1651        case Types.DECIMAL:
1652             // Oracle reports all numbers as NUMERIC. We check
1653             // the scale of the column and pick the right XSD type.
1654            if (scale == 0) {
1655                return XSD_INT;
1656            } else {
1657                return XSD_DECIMAL;
1658            }
1659        case Types.BIGINT:
1660            return XSD_INTEGER;
1661        // Real
1662        case Types.DOUBLE:
1663        case Types.FLOAT:
1664            return XSD_DOUBLE;
1665            // Date and time
1666        case Types.TIME:
1667        case Types.TIMESTAMP:
1668        case Types.DATE:
1669            return XSD_STRING;
1670            // Other
1671        default:
1672            return XSD_STRING;
1673        }
1674    }
1675
1676    private QueryResult executeQuery(XmlaRequest request)
1677        throws XmlaException
1678    {
1679        final String mdx = request.getStatement();
1680
1681        if (LOGGER.isDebugEnabled()) {
1682            LOGGER.debug("mdx: \"" + mdx + "\"");
1683        }
1684
1685        if ((mdx == null) || (mdx.length() == 0)) {
1686            return null;
1687        }
1688        checkFormat(request);
1689
1690        OlapConnection connection = null;
1691        PreparedOlapStatement statement = null;
1692        CellSet cellSet = null;
1693        boolean success = false;
1694        try {
1695            connection =
1696                getConnection(request, Collections.<String, String>emptyMap());
1697            getExtra(connection).setPreferList(connection);
1698            try {
1699                statement = connection.prepareOlapStatement(mdx);
1700            } catch (XmlaException ex) {
1701                throw ex;
1702            } catch (Exception ex) {
1703                throw new XmlaException(
1704                    CLIENT_FAULT_FC,
1705                    HSB_PARSE_QUERY_CODE,
1706                    HSB_PARSE_QUERY_FAULT_FS,
1707                    ex);
1708            }
1709            try {
1710                cellSet = statement.executeQuery();
1711
1712                final Format format = getFormat(request, null);
1713                final Content content = getContent(request);
1714                final Enumeration.ResponseMimeType responseMimeType =
1715                    getResponseMimeType(request);
1716                final MDDataSet dataSet;
1717                if (format == Format.Multidimensional) {
1718                    dataSet =
1719                        new MDDataSet_Multidimensional(
1720                            cellSet,
1721                            content != Content.DataIncludeDefaultSlicer,
1722                            responseMimeType
1723                            == Enumeration.ResponseMimeType.JSON);
1724                } else {
1725                    dataSet =
1726                        new MDDataSet_Tabular(cellSet);
1727                }
1728                success = true;
1729                return dataSet;
1730            } catch (XmlaException ex) {
1731                throw ex;
1732            } catch (Exception ex) {
1733                throw new XmlaException(
1734                    SERVER_FAULT_FC,
1735                    HSB_EXECUTE_QUERY_CODE,
1736                    HSB_EXECUTE_QUERY_FAULT_FS,
1737                    ex);
1738            }
1739        } finally {
1740            if (!success) {
1741                if (cellSet != null) {
1742                    try {
1743                        cellSet.close();
1744                    } catch (SQLException e) {
1745                        // ignore
1746                    }
1747                }
1748                if (statement != null) {
1749                    try {
1750                        statement.close();
1751                    } catch (SQLException e) {
1752                        // ignore
1753                    }
1754                }
1755                if (connection != null) {
1756                    try {
1757                        connection.close();
1758                    } catch (SQLException e) {
1759                        // ignore
1760                    }
1761                }
1762            }
1763        }
1764    }
1765
1766    private static Format getFormat(
1767        XmlaRequest request,
1768        Format defaultValue)
1769    {
1770        final String formatName =
1771            request.getProperties().get(
1772                PropertyDefinition.Format.name());
1773        return Util.lookup(
1774            Format.class,
1775            formatName, defaultValue);
1776    }
1777
1778    private static Content getContent(XmlaRequest request) {
1779        final String contentName =
1780            request.getProperties().get(
1781                PropertyDefinition.Content.name());
1782        return Util.lookup(
1783            Content.class,
1784            contentName,
1785            Content.DEFAULT);
1786    }
1787
1788    private static Enumeration.ResponseMimeType getResponseMimeType(
1789        XmlaRequest request)
1790    {
1791        Enumeration.ResponseMimeType mimeType =
1792            Enumeration.ResponseMimeType.MAP.get(
1793                request.getProperties().get(
1794                    PropertyDefinition.ResponseMimeType.name()));
1795        if (mimeType == null) {
1796            mimeType = Enumeration.ResponseMimeType.SOAP;
1797        }
1798        return mimeType;
1799    }
1800
1801    static abstract class MDDataSet implements QueryResult {
1802        protected final CellSet cellSet;
1803
1804        protected static final List<Property> cellProps =
1805            Arrays.asList(
1806                rename(StandardCellProperty.VALUE, "Value"),
1807                rename(StandardCellProperty.FORMATTED_VALUE, "FmtValue"),
1808                rename(StandardCellProperty.FORMAT_STRING, "FormatString"));
1809
1810        protected static final List<StandardCellProperty> cellPropLongs =
1811            Arrays.asList(
1812                StandardCellProperty.VALUE,
1813                StandardCellProperty.FORMATTED_VALUE,
1814                StandardCellProperty.FORMAT_STRING);
1815
1816        protected static final List<Property> defaultProps =
1817            Arrays.asList(
1818                rename(StandardMemberProperty.MEMBER_UNIQUE_NAME, "UName"),
1819                rename(StandardMemberProperty.MEMBER_CAPTION, "Caption"),
1820                rename(StandardMemberProperty.LEVEL_UNIQUE_NAME, "LName"),
1821                rename(StandardMemberProperty.LEVEL_NUMBER, "LNum"),
1822                rename(StandardMemberProperty.DISPLAY_INFO, "DisplayInfo"));
1823
1824        protected static final Map<String, StandardMemberProperty> longProps =
1825            new HashMap<String, StandardMemberProperty>();
1826
1827        static {
1828            longProps.put("UName", StandardMemberProperty.MEMBER_UNIQUE_NAME);
1829            longProps.put("Caption", StandardMemberProperty.MEMBER_CAPTION);
1830            longProps.put("LName", StandardMemberProperty.LEVEL_UNIQUE_NAME);
1831            longProps.put("LNum", StandardMemberProperty.LEVEL_NUMBER);
1832            longProps.put("DisplayInfo", StandardMemberProperty.DISPLAY_INFO);
1833        }
1834
1835        protected MDDataSet(CellSet cellSet) {
1836            this.cellSet = cellSet;
1837        }
1838
1839        public void close() throws SQLException {
1840            cellSet.getStatement().getConnection().close();
1841        }
1842
1843        private static Property rename(
1844            final Property property,
1845            final String name)
1846        {
1847            return new Property() {
1848                public Datatype getDatatype() {
1849                    return property.getDatatype();
1850                }
1851
1852                public Set<TypeFlag> getType() {
1853                    return property.getType();
1854                }
1855
1856                public ContentType getContentType() {
1857                    return property.getContentType();
1858                }
1859
1860                public String getName() {
1861                    return name;
1862                }
1863
1864                public String getUniqueName() {
1865                    return property.getUniqueName();
1866                }
1867
1868                public String getCaption() {
1869                    return property.getCaption();
1870                }
1871
1872                public String getDescription() {
1873                    return property.getDescription();
1874                }
1875
1876                public boolean isVisible() {
1877                    return property.isVisible();
1878                }
1879            };
1880        }
1881    }
1882
1883    static class MDDataSet_Multidimensional extends MDDataSet {
1884        private List<Hierarchy> slicerAxisHierarchies;
1885        private final boolean omitDefaultSlicerInfo;
1886        private final boolean json;
1887        private XmlaUtil.ElementNameEncoder encoder =
1888            XmlaUtil.ElementNameEncoder.INSTANCE;
1889        private XmlaExtra extra;
1890
1891        protected MDDataSet_Multidimensional(
1892            CellSet cellSet,
1893            boolean omitDefaultSlicerInfo,
1894            boolean json)
1895            throws SQLException
1896        {
1897            super(cellSet);
1898            this.omitDefaultSlicerInfo = omitDefaultSlicerInfo;
1899            this.json = json;
1900            this.extra = getExtra(cellSet.getStatement().getConnection());
1901        }
1902
1903        public void unparse(SaxWriter writer)
1904            throws SAXException, OlapException
1905        {
1906            olapInfo(writer);
1907            axes(writer);
1908            cellData(writer);
1909        }
1910
1911        public void metadata(SaxWriter writer) {
1912            writer.verbatim(MD_DATA_SET_XML_SCHEMA);
1913        }
1914
1915        private void olapInfo(SaxWriter writer) throws OlapException {
1916            // What are all of the cube's hierachies
1917            Cube cube = cellSet.getMetaData().getCube();
1918
1919            writer.startElement("OlapInfo");
1920            writer.startElement("CubeInfo");
1921            writer.startElement("Cube");
1922            writer.textElement("CubeName", cube.getName());
1923            writer.endElement();
1924            writer.endElement(); // CubeInfo
1925
1926            // create AxesInfo for axes
1927            // -----------
1928            writer.startSequence("AxesInfo", "AxisInfo");
1929            final List<CellSetAxis> axes = cellSet.getAxes();
1930            List<Hierarchy> axisHierarchyList = new ArrayList<Hierarchy>();
1931            for (int i = 0; i < axes.size(); i++) {
1932                List<Hierarchy> hiers =
1933                    axisInfo(writer, axes.get(i), "Axis" + i);
1934                axisHierarchyList.addAll(hiers);
1935            }
1936            ///////////////////////////////////////////////
1937            // create AxesInfo for slicer axes
1938            //
1939            List<Hierarchy> hierarchies;
1940            CellSetAxis slicerAxis = cellSet.getFilterAxis();
1941            if (omitDefaultSlicerInfo) {
1942                hierarchies =
1943                    axisInfo(
1944                        writer, slicerAxis, "SlicerAxis");
1945            } else {
1946                // The slicer axes contains the default hierarchy
1947                // of each dimension not seen on another axis.
1948                List<Dimension> unseenDimensionList =
1949                    new ArrayList<Dimension>(cube.getDimensions());
1950                for (Hierarchy hier1 : axisHierarchyList) {
1951                    unseenDimensionList.remove(hier1.getDimension());
1952                }
1953                hierarchies = new ArrayList<Hierarchy>();
1954                for (Dimension dimension : unseenDimensionList) {
1955                    for (Hierarchy hierarchy : dimension.getHierarchies()) {
1956                        hierarchies.add(hierarchy);
1957                    }
1958                }
1959                writer.startElement(
1960                    "AxisInfo",
1961                    "name", "SlicerAxis");
1962                writeHierarchyInfo(
1963                    writer, hierarchies,
1964                    getProps(slicerAxis.getAxisMetaData()));
1965                writer.endElement(); // AxisInfo
1966            }
1967            slicerAxisHierarchies = hierarchies;
1968            //
1969            ///////////////////////////////////////////////
1970
1971            writer.endSequence(); // AxesInfo
1972
1973            // -----------
1974            writer.startElement("CellInfo");
1975            cellProperty(writer, StandardCellProperty.VALUE, true, "Value");
1976            cellProperty(
1977                writer, StandardCellProperty.FORMATTED_VALUE, true, "FmtValue");
1978            cellProperty(
1979                writer, StandardCellProperty.FORMAT_STRING, true,
1980                "FormatString");
1981            cellProperty(
1982                writer, StandardCellProperty.LANGUAGE, false, "Language");
1983            cellProperty(
1984                writer, StandardCellProperty.BACK_COLOR, false, "BackColor");
1985            cellProperty(
1986                writer, StandardCellProperty.FORE_COLOR, false, "ForeColor");
1987            cellProperty(
1988                writer, StandardCellProperty.FONT_FLAGS, false, "FontFlags");
1989            writer.endElement(); // CellInfo
1990            // -----------
1991            writer.endElement(); // OlapInfo
1992        }
1993
1994        private void cellProperty(
1995            SaxWriter writer,
1996            StandardCellProperty cellProperty,
1997            boolean evenEmpty,
1998            String elementName)
1999        {
2000            if (extra.shouldReturnCellProperty(
2001                    cellSet, cellProperty, evenEmpty))
2002            {
2003                writer.element(
2004                    elementName,
2005                    "name", cellProperty.getName());
2006            }
2007        }
2008
2009        private List<Hierarchy> axisInfo(
2010            SaxWriter writer,
2011            CellSetAxis axis,
2012            String axisName)
2013        {
2014            writer.startElement(
2015                "AxisInfo",
2016                "name", axisName);
2017
2018            List<Hierarchy> hierarchies;
2019            Iterator<org.olap4j.Position> it = axis.getPositions().iterator();
2020            if (it.hasNext()) {
2021                final org.olap4j.Position position = it.next();
2022                hierarchies = new ArrayList<Hierarchy>();
2023                for (Member member : position.getMembers()) {
2024                    hierarchies.add(member.getHierarchy());
2025                }
2026            } else {
2027                hierarchies = axis.getAxisMetaData().getHierarchies();
2028            }
2029            List<Property> props = getProps(axis.getAxisMetaData());
2030            writeHierarchyInfo(writer, hierarchies, props);
2031
2032            writer.endElement(); // AxisInfo
2033
2034            return hierarchies;
2035        }
2036
2037        private void writeHierarchyInfo(
2038            SaxWriter writer,
2039            List<Hierarchy> hierarchies,
2040            List<Property> props)
2041        {
2042            writer.startSequence(null, "HierarchyInfo");
2043            for (Hierarchy hierarchy : hierarchies) {
2044                writer.startElement(
2045                    "HierarchyInfo",
2046                    "name", hierarchy.getName());
2047                for (final Property prop : props) {
2048                    final String encodedProp =
2049                        encoder.encode(prop.getName());
2050                    final Object[] attributes = getAttributes(prop, hierarchy);
2051                    writer.element(encodedProp, attributes);
2052                }
2053                writer.endElement(); // HierarchyInfo
2054            }
2055            writer.endSequence(); // "HierarchyInfo"
2056        }
2057
2058        private Object[] getAttributes(Property prop, Hierarchy hierarchy) {
2059            Property longProp = longProps.get(prop.getName());
2060            if (longProp == null) {
2061                longProp = prop;
2062            }
2063            List<Object> values = new ArrayList<Object>();
2064            values.add("name");
2065            values.add(
2066                hierarchy.getUniqueName()
2067                + "."
2068                + Util.quoteMdxIdentifier(longProp.getName()));
2069            if (longProp == prop) {
2070                // Adding type attribute to the optional properties
2071                values.add("type");
2072                values.add(getXsdType(longProp));
2073            }
2074            return values.toArray();
2075        }
2076
2077        private String getXsdType(Property property) {
2078            Datatype datatype = property.getDatatype();
2079            switch (datatype) {
2080            case UNSIGNED_INTEGER:
2081                return RowsetDefinition.Type.UnsignedInteger.columnType;
2082            case BOOLEAN:
2083                return RowsetDefinition.Type.Boolean.columnType;
2084            default:
2085                return RowsetDefinition.Type.String.columnType;
2086            }
2087        }
2088
2089        private void axes(SaxWriter writer) throws OlapException {
2090            writer.startSequence("Axes", "Axis");
2091            //axis(writer, result.getSlicerAxis(), "SlicerAxis");
2092            final List<CellSetAxis> axes = cellSet.getAxes();
2093            for (int i = 0; i < axes.size(); i++) {
2094                final CellSetAxis axis = axes.get(i);
2095                final List<Property> props = getProps(axis.getAxisMetaData());
2096                axis(writer, axis, props, "Axis" + i);
2097            }
2098
2099            ////////////////////////////////////////////
2100            // now generate SlicerAxis information
2101            //
2102            if (omitDefaultSlicerInfo) {
2103                CellSetAxis slicerAxis = cellSet.getFilterAxis();
2104                // We always write a slicer axis. There are two 'empty' cases:
2105                // zero positions (which happens when the WHERE clause evalutes
2106                // to an empty set) or one position containing a tuple of zero
2107                // members (which happens when there is no WHERE clause) and we
2108                // need to be able to distinguish between the two.
2109                axis(
2110                    writer,
2111                    slicerAxis,
2112                    getProps(slicerAxis.getAxisMetaData()),
2113                    "SlicerAxis");
2114            } else {
2115                List<Hierarchy> hierarchies = slicerAxisHierarchies;
2116                writer.startElement(
2117                    "Axis",
2118                    "name", "SlicerAxis");
2119                writer.startSequence("Tuples", "Tuple");
2120                writer.startSequence("Tuple", "Member");
2121
2122                Map<String, Integer> memberMap = new HashMap<String, Integer>();
2123                Member positionMember;
2124                CellSetAxis slicerAxis = cellSet.getFilterAxis();
2125                final List<Position> slicerPositions =
2126                    slicerAxis.getPositions();
2127                if (slicerPositions != null
2128                    && slicerPositions.size() > 0)
2129                {
2130                    final Position pos0 = slicerPositions.get(0);
2131                    int i = 0;
2132                    for (Member member : pos0.getMembers()) {
2133                        memberMap.put(member.getHierarchy().getName(), i++);
2134                    }
2135                }
2136
2137                final List<Member> slicerMembers =
2138                    slicerPositions.isEmpty()
2139                        ? Collections.<Member>emptyList()
2140                        : slicerPositions.get(0).getMembers();
2141                for (Hierarchy hierarchy : hierarchies) {
2142                    // Find which member is on the slicer.
2143                    // If it's not explicitly there, use the default member.
2144                    Member member = hierarchy.getDefaultMember();
2145                    final Integer indexPosition =
2146                        memberMap.get(hierarchy.getName());
2147                    if (indexPosition != null) {
2148                        positionMember = slicerMembers.get(indexPosition);
2149                    } else {
2150                        positionMember = null;
2151                    }
2152                    for (Member slicerMember : slicerMembers) {
2153                        if (slicerMember.getHierarchy().equals(hierarchy)) {
2154                            member = slicerMember;
2155                            break;
2156                        }
2157                    }
2158
2159                    if (member != null) {
2160                        if (positionMember != null) {
2161                            writeMember(
2162                                writer, positionMember, null,
2163                                slicerPositions.get(0), indexPosition,
2164                                getProps(slicerAxis.getAxisMetaData()));
2165                        } else {
2166                            slicerAxis(
2167                                writer, member,
2168                                getProps(slicerAxis.getAxisMetaData()));
2169                        }
2170                    } else {
2171                        LOGGER.warn(
2172                            "Can not create SlicerAxis: "
2173                            + "null default member for Hierarchy "
2174                            + hierarchy.getUniqueName());
2175                    }
2176                }
2177                writer.endSequence(); // Tuple
2178                writer.endSequence(); // Tuples
2179                writer.endElement(); // Axis
2180            }
2181
2182            //
2183            ////////////////////////////////////////////
2184
2185            writer.endSequence(); // Axes
2186        }
2187
2188        private List<Property> getProps(CellSetAxisMetaData queryAxis) {
2189            if (queryAxis == null) {
2190                return defaultProps;
2191            }
2192            return CompositeList.of(
2193                defaultProps,
2194                queryAxis.getProperties());
2195        }
2196
2197        private void axis(
2198            SaxWriter writer,
2199            CellSetAxis axis,
2200            List<Property> props,
2201            String axisName) throws OlapException
2202        {
2203            writer.startElement(
2204                "Axis",
2205                "name", axisName);
2206            writer.startSequence("Tuples", "Tuple");
2207
2208            List<Position> positions = axis.getPositions();
2209            Iterator<Position> pit = positions.iterator();
2210            Position prevPosition = null;
2211            Position position = pit.hasNext() ? pit.next() : null;
2212            Position nextPosition = pit.hasNext() ? pit.next() : null;
2213            while (position != null) {
2214                writer.startSequence("Tuple", "Member");
2215                int k = 0;
2216                for (Member member : position.getMembers()) {
2217                    writeMember(
2218                        writer, member, prevPosition, nextPosition, k++, props);
2219                }
2220                writer.endSequence(); // Tuple
2221                prevPosition = position;
2222                position = nextPosition;
2223                nextPosition = pit.hasNext() ? pit.next() : null;
2224            }
2225            writer.endSequence(); // Tuples
2226            writer.endElement(); // Axis
2227        }
2228
2229        private void writeMember(
2230            SaxWriter writer,
2231            Member member,
2232            Position prevPosition,
2233            Position nextPosition,
2234            int k,
2235            List<Property> props)
2236            throws OlapException
2237        {
2238            writer.startElement(
2239                "Member",
2240                "Hierarchy", member.getHierarchy().getName());
2241            for (Property prop : props) {
2242                Object value;
2243                Property longProp = longProps.get(prop.getName());
2244                if (longProp == null) {
2245                    longProp = prop;
2246                }
2247                if (longProp == StandardMemberProperty.DISPLAY_INFO) {
2248                    Integer childrenCard =
2249                        (Integer) member.getPropertyValue(
2250                            StandardMemberProperty.CHILDREN_CARDINALITY);
2251                    value = calculateDisplayInfo(
2252                        prevPosition,
2253                        nextPosition,
2254                        member, k, childrenCard);
2255                } else if (longProp == StandardMemberProperty.DEPTH) {
2256                    value = member.getDepth();
2257                } else {
2258                    value = member.getPropertyValue(longProp);
2259                }
2260                if (value != null) {
2261                    writer.textElement(
2262                        encoder.encode(prop.getName()), value);
2263                }
2264            }
2265
2266            writer.endElement(); // Member
2267        }
2268
2269        private void slicerAxis(
2270            SaxWriter writer, Member member, List<Property> props)
2271            throws OlapException
2272        {
2273            writer.startElement(
2274                "Member",
2275                "Hierarchy", member.getHierarchy().getName());
2276            for (Property prop : props) {
2277                Object value;
2278                Property longProp = longProps.get(prop.getName());
2279                if (longProp == null) {
2280                    longProp = prop;
2281                }
2282                if (longProp == StandardMemberProperty.DISPLAY_INFO) {
2283                    Integer childrenCard =
2284                        (Integer) member.getPropertyValue(
2285                            StandardMemberProperty.CHILDREN_CARDINALITY);
2286                    // NOTE: don't know if this is correct for
2287                    // SlicerAxis
2288                    int displayInfo = 0xffff & childrenCard;
2289/*
2290                    int displayInfo =
2291                        calculateDisplayInfo((j == 0 ? null : positions[j - 1]),
2292                          (j + 1 == positions.length ? null : positions[j + 1]),
2293                          member, k, childrenCard.intValue());
2294*/
2295                    value = displayInfo;
2296                } else if (longProp == StandardMemberProperty.DEPTH) {
2297                    value = member.getDepth();
2298                } else {
2299                    value = member.getPropertyValue(longProp);
2300                }
2301                if (value != null) {
2302                    writer.textElement(
2303                        encoder.encode(prop.getName()), value);
2304                }
2305            }
2306            writer.endElement(); // Member
2307        }
2308
2309        private int calculateDisplayInfo(
2310            Position prevPosition,
2311            Position nextPosition,
2312            Member currentMember,
2313            int memberOrdinal,
2314            int childrenCount)
2315        {
2316            int displayInfo = 0xffff & childrenCount;
2317
2318            if (nextPosition != null) {
2319                String currentUName = currentMember.getUniqueName();
2320                Member nextMember =
2321                    nextPosition.getMembers().get(memberOrdinal);
2322                String nextParentUName = parentUniqueName(nextMember);
2323                if (currentUName.equals(nextParentUName)) {
2324                    displayInfo |= 0x10000;
2325                }
2326            }
2327            if (prevPosition != null) {
2328                String currentParentUName = parentUniqueName(currentMember);
2329                Member prevMember =
2330                    prevPosition.getMembers().get(memberOrdinal);
2331                String prevParentUName = parentUniqueName(prevMember);
2332                if (currentParentUName != null
2333                    && currentParentUName.equals(prevParentUName))
2334                {
2335                    displayInfo |= 0x20000;
2336                }
2337            }
2338            return displayInfo;
2339        }
2340
2341        private String parentUniqueName(Member member) {
2342            final Member parent = member.getParentMember();
2343            if (parent == null) {
2344                return null;
2345            }
2346            return parent.getUniqueName();
2347        }
2348
2349        private void cellData(SaxWriter writer) {
2350            writer.startSequence("CellData", "Cell");
2351            final int axisCount = cellSet.getAxes().size();
2352            List<Integer> pos = new ArrayList<Integer>();
2353            for (int i = 0; i < axisCount; i++) {
2354                pos.add(-1);
2355            }
2356            int[] cellOrdinal = new int[] {0};
2357
2358            int axisOrdinal = axisCount - 1;
2359            recurse(writer, pos, axisOrdinal, cellOrdinal);
2360
2361            writer.endSequence(); // CellData
2362        }
2363
2364        private void recurse(
2365            SaxWriter writer,
2366            List<Integer> pos,
2367            int axisOrdinal,
2368            int[] cellOrdinal)
2369        {
2370            if (axisOrdinal < 0) {
2371                emitCell(writer, pos, cellOrdinal[0]++);
2372            } else {
2373                CellSetAxis axis = cellSet.getAxes().get(axisOrdinal);
2374                List<Position> positions = axis.getPositions();
2375                for (int i = 0, n = positions.size(); i < n; i++) {
2376                    pos.set(axisOrdinal, i);
2377                    recurse(writer, pos, axisOrdinal - 1, cellOrdinal);
2378                }
2379            }
2380        }
2381
2382        private void emitCell(
2383            SaxWriter writer,
2384            List<Integer> pos,
2385            int ordinal)
2386        {
2387            Cell cell = cellSet.getCell(pos);
2388            if (cell.isNull() && ordinal != 0) {
2389                // Ignore null cell like MS AS, except for Oth ordinal
2390                return;
2391            }
2392
2393            writer.startElement(
2394                "Cell",
2395                "CellOrdinal", ordinal);
2396            for (int i = 0; i < cellProps.size(); i++) {
2397                Property cellPropLong = cellPropLongs.get(i);
2398                Object value = cell.getPropertyValue(cellPropLong);
2399                if (value == null) {
2400                    continue;
2401                }
2402                if (!extra.shouldReturnCellProperty(
2403                        cellSet, cellPropLong, true))
2404                {
2405                    continue;
2406                }
2407
2408                if (!json && cellPropLong == StandardCellProperty.VALUE) {
2409                    if (cell.isNull()) {
2410                        // Return cell without value as in case of AS2005
2411                        continue;
2412                    }
2413                    final String dataType =
2414                        (String) cell.getPropertyValue(
2415                            StandardCellProperty.DATATYPE);
2416                    final ValueInfo vi = new ValueInfo(dataType, value);
2417                    final String valueType = vi.valueType;
2418                    final String valueString;
2419                    if (vi.isDecimal) {
2420                        valueString =
2421                            XmlaUtil.normalizeNumericString(
2422                                vi.value.toString());
2423                    } else {
2424                        valueString = vi.value.toString();
2425                    }
2426
2427                    writer.startElement(
2428                        cellProps.get(i).getName(),
2429                        "xsi:type", valueType);
2430                    writer.characters(valueString);
2431                    writer.endElement();
2432                } else {
2433                    writer.textElement(cellProps.get(i).getName(), value);
2434                }
2435            }
2436            writer.endElement(); // Cell
2437        }
2438    }
2439
2440    static abstract class ColumnHandler {
2441        protected final String name;
2442        protected final String encodedName;
2443
2444        protected ColumnHandler(String name) {
2445            this.name = name;
2446            this.encodedName =
2447                XmlaUtil.ElementNameEncoder.INSTANCE.encode(name);
2448        }
2449
2450        abstract void write(SaxWriter writer, Cell cell, Member[] members)
2451            throws OlapException;
2452        abstract void metadata(SaxWriter writer);
2453    }
2454
2455
2456    /**
2457     * Callback to handle one column, representing the combination of a
2458     * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME])
2459     * in a flattened dataset.
2460     */
2461    static class CellColumnHandler extends ColumnHandler {
2462
2463        CellColumnHandler(String name) {
2464            super(name);
2465        }
2466
2467        public void metadata(SaxWriter writer) {
2468            writer.element(
2469                "xsd:element",
2470                "minOccurs", 0,
2471                "name", encodedName,
2472                "sql:field", name);
2473        }
2474
2475        public void write(
2476            SaxWriter writer, Cell cell, Member[] members)
2477        {
2478            if (cell.isNull()) {
2479                return;
2480            }
2481            Object value = cell.getValue();
2482            final String dataType = (String)
2483                cell.getPropertyValue(StandardCellProperty.DATATYPE);
2484
2485            final ValueInfo vi = new ValueInfo(dataType, value);
2486            final String valueType = vi.valueType;
2487            value = vi.value;
2488            boolean isDecimal = vi.isDecimal;
2489
2490            String valueString = value.toString();
2491
2492            writer.startElement(
2493                encodedName,
2494                "xsi:type", valueType);
2495            if (isDecimal) {
2496                valueString = XmlaUtil.normalizeNumericString(valueString);
2497            }
2498            writer.characters(valueString);
2499            writer.endElement();
2500        }
2501    }
2502
2503    /**
2504     * Callback to handle one column, representing the combination of a
2505     * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME])
2506     * in a flattened dataset.
2507     */
2508    static class MemberColumnHandler extends ColumnHandler {
2509        private final Property property;
2510        private final Level level;
2511        private final int memberOrdinal;
2512
2513        public MemberColumnHandler(
2514            Property property, Level level, int memberOrdinal)
2515        {
2516            super(
2517                level.getUniqueName() + "."
2518                + Util.quoteMdxIdentifier(property.getName()));
2519            this.property = property;
2520            this.level = level;
2521            this.memberOrdinal = memberOrdinal;
2522        }
2523
2524        public void metadata(SaxWriter writer) {
2525            writer.element(
2526                "xsd:element",
2527                "minOccurs", 0,
2528                "name", encodedName,
2529                "sql:field", name,
2530                "type", XSD_STRING);
2531        }
2532
2533        public void write(
2534            SaxWriter writer, Cell cell, Member[] members) throws OlapException
2535        {
2536            Member member = members[memberOrdinal];
2537            final int depth = level.getDepth();
2538            if (member.getDepth() < depth) {
2539                // This column deals with a level below the current member.
2540                // There is no value to write.
2541                return;
2542            }
2543            while (member.getDepth() > depth) {
2544                member = member.getParentMember();
2545            }
2546            final Object propertyValue = member.getPropertyValue(property);
2547            if (propertyValue == null) {
2548                return;
2549            }
2550
2551            writer.startElement(encodedName);
2552            writer.characters(propertyValue.toString());
2553            writer.endElement();
2554        }
2555    }
2556
2557    static class MDDataSet_Tabular extends MDDataSet {
2558        private final boolean empty;
2559        private final int[] pos;
2560        private final List<Integer> posList;
2561        private final int axisCount;
2562        private int cellOrdinal;
2563
2564        private static final List<Property> MemberCaptionIdArray =
2565            Collections.<Property>singletonList(
2566                StandardMemberProperty.MEMBER_CAPTION);
2567
2568        private final Member[] members;
2569        private final ColumnHandler[] columnHandlers;
2570
2571        public MDDataSet_Tabular(CellSet cellSet) {
2572            super(cellSet);
2573            final List<CellSetAxis> axes = cellSet.getAxes();
2574            axisCount = axes.size();
2575            pos = new int[axisCount];
2576            posList = new IntList(pos);
2577
2578            // Count dimensions, and deduce list of levels which appear on
2579            // non-COLUMNS axes.
2580            boolean empty = false;
2581            int dimensionCount = 0;
2582            for (int i = axes.size() - 1; i > 0; i--) {
2583                CellSetAxis axis = axes.get(i);
2584                if (axis.getPositions().size() == 0) {
2585                    // If any axis is empty, the whole data set is empty.
2586                    empty = true;
2587                    continue;
2588                }
2589                dimensionCount +=
2590                    axis.getPositions().get(0).getMembers().size();
2591            }
2592            this.empty = empty;
2593
2594            // Build a list of the lowest level used on each non-COLUMNS axis.
2595            Level[] levels = new Level[dimensionCount];
2596            List<ColumnHandler> columnHandlerList =
2597                new ArrayList<ColumnHandler>();
2598            int memberOrdinal = 0;
2599            if (!empty) {
2600                for (int i = axes.size() - 1; i > 0; i--) {
2601                    final CellSetAxis axis = axes.get(i);
2602                    final int z0 = memberOrdinal; // save ordinal so can rewind
2603                    final List<Position> positions = axis.getPositions();
2604                    int jj = 0;
2605                    for (Position position : positions) {
2606                        memberOrdinal = z0; // rewind to start
2607                        for (Member member : position.getMembers()) {
2608                            if (jj == 0
2609                                || member.getLevel().getDepth()
2610                                > levels[memberOrdinal].getDepth())
2611                            {
2612                                levels[memberOrdinal] = member.getLevel();
2613                            }
2614                            memberOrdinal++;
2615                        }
2616                        jj++;
2617                    }
2618
2619                    // Now we know the lowest levels on this axis, add
2620                    // properties.
2621                    List<Property> dimProps =
2622                        axis.getAxisMetaData().getProperties();
2623                    if (dimProps.size() == 0) {
2624                        dimProps = MemberCaptionIdArray;
2625                    }
2626                    for (int j = z0; j < memberOrdinal; j++) {
2627                        Level level = levels[j];
2628                        for (int k = 0; k <= level.getDepth(); k++) {
2629                            final Level level2 =
2630                                level.getHierarchy().getLevels().get(k);
2631                            if (level2.getLevelType() == Level.Type.ALL) {
2632                                continue;
2633                            }
2634                            for (Property dimProp : dimProps) {
2635                                columnHandlerList.add(
2636                                    new MemberColumnHandler(
2637                                        dimProp, level2, j));
2638                            }
2639                        }
2640                    }
2641                }
2642            }
2643            this.members = new Member[memberOrdinal + 1];
2644
2645            // Deduce the list of column headings.
2646            if (axes.size() > 0) {
2647                CellSetAxis columnsAxis = axes.get(0);
2648                for (Position position : columnsAxis.getPositions()) {
2649                    String name = null;
2650                    int j = 0;
2651                    for (Member member : position.getMembers()) {
2652                        if (j == 0) {
2653                            name = member.getUniqueName();
2654                        } else {
2655                            name = name + "." + member.getUniqueName();
2656                        }
2657                        j++;
2658                    }
2659                    columnHandlerList.add(
2660                        new CellColumnHandler(name));
2661                }
2662            }
2663
2664            this.columnHandlers =
2665                columnHandlerList.toArray(
2666                    new ColumnHandler[columnHandlerList.size()]);
2667        }
2668
2669        public void metadata(SaxWriter writer) {
2670            writer.startElement(
2671                "xsd:schema",
2672                "xmlns:xsd", NS_XSD,
2673                "targetNamespace", NS_XMLA_ROWSET,
2674                "xmlns", NS_XMLA_ROWSET,
2675                "xmlns:xsi", NS_XSI,
2676                "xmlns:sql", NS_XML_SQL,
2677                "elementFormDefault", "qualified");
2678
2679            { // <root>
2680                writer.startElement(
2681                    "xsd:element",
2682                    "name", "root");
2683                writer.startElement("xsd:complexType");
2684                writer.startElement("xsd:sequence");
2685                writer.element(
2686                    "xsd:element",
2687                    "maxOccurs", "unbounded",
2688                    "minOccurs", 0,
2689                    "name", "row",
2690                    "type", "row");
2691                writer.endElement(); // xsd:sequence
2692                writer.endElement(); // xsd:complexType
2693                writer.endElement(); // xsd:element name=root
2694            }
2695
2696            { // xsd:simpleType name="uuid"
2697                writer.startElement(
2698                    "xsd:simpleType",
2699                    "name", "uuid");
2700                writer.startElement(
2701                    "xsd:restriction",
2702                    "base", XSD_STRING);
2703                writer.element(
2704                    "xsd:pattern",
2705                    "value", RowsetDefinition.UUID_PATTERN);
2706                writer.endElement(); // xsd:restriction
2707                writer.endElement(); // xsd:simpleType
2708            }
2709
2710            { // xsd:complexType name="row"
2711                writer.startElement(
2712                    "xsd:complexType",
2713                    "name", "row");
2714                writer.startElement("xsd:sequence");
2715                for (ColumnHandler columnHandler : columnHandlers) {
2716                    columnHandler.metadata(writer);
2717                }
2718                writer.endElement(); // xsd:sequence
2719                writer.endElement(); // xsd:complexType
2720            }
2721            writer.endElement(); // xsd:schema
2722        }
2723
2724        public void unparse(SaxWriter writer)
2725            throws SAXException, OlapException
2726        {
2727            if (empty) {
2728                return;
2729            }
2730            cellData(writer);
2731        }
2732
2733        private void cellData(SaxWriter writer)
2734            throws SAXException, OlapException
2735        {
2736            cellOrdinal = 0;
2737            iterate(writer);
2738        }
2739
2740        /**
2741         * Iterates over the resust writing tabular rows.
2742         *
2743         * @param writer Writer
2744         * @throws org.xml.sax.SAXException on error
2745         */
2746        private void iterate(SaxWriter writer)
2747            throws SAXException, OlapException
2748        {
2749            switch (axisCount) {
2750            case 0:
2751                // For MDX like: SELECT FROM Sales
2752                emitCell(writer, cellSet.getCell(posList));
2753                return;
2754            default:
2755//                throw new SAXException("Too many axes: " + axisCount);
2756                iterate(writer, axisCount - 1, 0);
2757                break;
2758            }
2759        }
2760
2761        private void iterate(SaxWriter writer, int axis, final int xxx)
2762            throws OlapException
2763        {
2764            final List<Position> positions =
2765                cellSet.getAxes().get(axis).getPositions();
2766            int axisLength = axis == 0 ? 1 : positions.size();
2767
2768            for (int i = 0; i < axisLength; i++) {
2769                final Position position = positions.get(i);
2770                int ho = xxx;
2771                final List<Member> members = position.getMembers();
2772                for (int j = 0;
2773                     j < members.size() && ho < this.members.length;
2774                     j++, ho++)
2775                {
2776                    this.members[ho] = position.getMembers().get(j);
2777                }
2778
2779                ++cellOrdinal;
2780                Util.discard(cellOrdinal);
2781
2782                if (axis >= 2) {
2783                    iterate(writer, axis - 1, ho);
2784                } else {
2785                    writer.startElement("row");// abrimos la fila
2786                    pos[axis] = i; // coordenadas: fila i
2787                    pos[0] = 0; // coordenadas (0,i): columna 0
2788                    for (ColumnHandler columnHandler : columnHandlers) {
2789                        if (columnHandler instanceof MemberColumnHandler) {
2790                            columnHandler.write(writer, null, this.members);
2791                        } else if (columnHandler instanceof CellColumnHandler) {
2792                            columnHandler.write(
2793                                writer, cellSet.getCell(posList), null);
2794                            pos[0]++;// next col.
2795                        }
2796                    }
2797                    writer.endElement(); // cerramos la fila
2798                }
2799            }
2800        }
2801
2802        private void emitCell(SaxWriter writer, Cell cell)
2803            throws OlapException
2804        {
2805            ++cellOrdinal;
2806            Util.discard(cellOrdinal);
2807
2808            // Ignore empty cells.
2809            final Object cellValue = cell.getValue();
2810            if (cellValue == null) {
2811                return;
2812            }
2813
2814            writer.startElement("row");
2815            for (ColumnHandler columnHandler : columnHandlers) {
2816                columnHandler.write(writer, cell, members);
2817            }
2818            writer.endElement();
2819        }
2820    }
2821
2822    private void discover(XmlaRequest request, XmlaResponse response)
2823        throws XmlaException
2824    {
2825        final RowsetDefinition rowsetDefinition =
2826            RowsetDefinition.valueOf(request.getRequestType());
2827        Rowset rowset = rowsetDefinition.getRowset(request, this);
2828
2829        Format format = getFormat(request, Format.Tabular);
2830        if (format != Format.Tabular) {
2831            throw new XmlaException(
2832                CLIENT_FAULT_FC,
2833                HSB_DISCOVER_FORMAT_CODE,
2834                HSB_DISCOVER_FORMAT_FAULT_FS,
2835                new UnsupportedOperationException(
2836                    "<Format>: only 'Tabular' allowed in Discover method "
2837                    + "type"));
2838        }
2839        final Content content = getContent(request);
2840
2841        SaxWriter writer = response.getWriter();
2842        writer.startDocument();
2843
2844        writer.startElement(
2845            prefix + ":DiscoverResponse",
2846            "xmlns:" + prefix, NS_XMLA);
2847        writer.startElement(prefix + ":return");
2848        writer.startElement(
2849            "root",
2850            "xmlns", NS_XMLA_ROWSET,
2851            "xmlns:xsi", NS_XSI,
2852            "xmlns:xsd", NS_XSD,
2853            "xmlns:EX", NS_XMLA_EX);
2854
2855        switch (content) {
2856        case Schema:
2857        case SchemaData:
2858            rowset.rowsetDefinition.writeRowsetXmlSchema(writer);
2859            break;
2860        }
2861
2862        try {
2863            switch (content) {
2864            case Data:
2865            case SchemaData:
2866                rowset.unparse(response);
2867                break;
2868            }
2869        } catch (XmlaException xex) {
2870            throw xex;
2871        } catch (Throwable t) {
2872            throw new XmlaException(
2873                SERVER_FAULT_FC,
2874                HSB_DISCOVER_UNPARSE_CODE,
2875                HSB_DISCOVER_UNPARSE_FAULT_FS,
2876                t);
2877        } finally {
2878            // keep the tags balanced, even if there's an error
2879            try {
2880                writer.endElement();
2881                writer.endElement();
2882                writer.endElement();
2883            } catch (Throwable e) {
2884                // Ignore any errors balancing the tags. The original exception
2885                // is more important.
2886            }
2887        }
2888
2889        writer.endDocument();
2890    }
2891
2892    /**
2893     * Gets a Connection given a catalog (and implicitly the catalog's data
2894     * source) and the name of a user role.
2895     *
2896     * <p>If you want to pass in a role object, and you are making the call
2897     * within the same JVM (i.e. not RPC), register the role using
2898     * {@link mondrian.olap.MondrianServer#getLockBox()} and pass in the moniker
2899     * for the generated lock box entry. The server will retrieve the role from
2900     * the moniker.
2901     *
2902     * @param catalog Catalog name
2903     * @param schema Schema name
2904     * @param role User role name
2905     * @return Connection
2906     * @throws XmlaException If error occurs
2907     */
2908    protected OlapConnection getConnection(
2909        String catalog,
2910        String schema,
2911        final String role)
2912        throws XmlaException
2913    {
2914        return this.getConnection(
2915            catalog, schema, role,
2916            new Properties());
2917    }
2918
2919    /**
2920     * Gets a Connection given a catalog (and implicitly the catalog's data
2921     * source) and the name of a user role.
2922     *
2923     * <p>If you want to pass in a role object, and you are making the call
2924     * within the same JVM (i.e. not RPC), register the role using
2925     * {@link mondrian.olap.MondrianServer#getLockBox()} and pass in the moniker
2926     * for the generated lock box entry. The server will retrieve the role from
2927     * the moniker.
2928     *
2929     * @param catalog Catalog name
2930     * @param schema Schema name
2931     * @param role User role name
2932     * @param props Properties to pass down to the native driver.
2933     * @return Connection
2934     * @throws XmlaException If error occurs
2935     */
2936    protected OlapConnection getConnection(
2937        String catalog,
2938        String schema,
2939        final String role,
2940        Properties props)
2941        throws XmlaException
2942    {
2943        try {
2944            return
2945                connectionFactory.getConnection(
2946                    catalog, schema, role, props);
2947        } catch (SecurityException e) {
2948            throw new XmlaException(
2949                CLIENT_FAULT_FC,
2950                HSB_ACCESS_DENIED_CODE,
2951                HSB_ACCESS_DENIED_FAULT_FS,
2952                e);
2953        } catch (SQLException e) {
2954            throw new XmlaException(
2955                CLIENT_FAULT_FC,
2956                HSB_CONNECTION_DATA_SOURCE_CODE,
2957                HSB_CONNECTION_DATA_SOURCE_FAULT_FS,
2958                e);
2959        }
2960    }
2961
2962    private static class IntList extends AbstractList<Integer> {
2963        private final int[] ints;
2964
2965        IntList(int[] ints) {
2966            this.ints = ints;
2967        }
2968
2969        public Integer get(int index) {
2970            return ints[index];
2971        }
2972
2973        public int size() {
2974            return ints.length;
2975        }
2976    }
2977
2978    /**
2979     * Extra support for XMLA server. If a connection provides this interface,
2980     * the XMLA server will call methods in this interface instead of relying
2981     * on the core olap4j interface.
2982     *
2983     * <p>The {@link mondrian.xmla.XmlaHandler.XmlaExtraImpl} class provides
2984     * a default implementation that uses the olap4j interface exclusively.
2985     */
2986    public interface XmlaExtra {
2987
2988        ResultSet executeDrillthrough(
2989            OlapStatement olapStatement,
2990            String mdx,
2991            boolean advanced,
2992            String tabFields,
2993            int[] rowCountSlot) throws SQLException;
2994
2995        void setPreferList(OlapConnection connection);
2996
2997        Date getSchemaLoadDate(Schema schema);
2998
2999        int getLevelCardinality(Level level) throws OlapException;
3000
3001        void getSchemaFunctionList(
3002            List<FunctionDefinition> funDefs,
3003            Schema schema,
3004            Util.Functor1<Boolean, String> functionFilter);
3005
3006        int getHierarchyCardinality(Hierarchy hierarchy) throws OlapException;
3007
3008        int getHierarchyStructure(Hierarchy hierarchy);
3009
3010        boolean isHierarchyParentChild(Hierarchy hierarchy);
3011
3012        int getMeasureAggregator(Member member);
3013
3014        void checkMemberOrdinal(Member member) throws OlapException;
3015
3016        /**
3017         * Returns whether we should return a cell property in the XMLA result.
3018         *
3019         * @param cellSet Cell set
3020         * @param cellProperty Cell property definition
3021         * @param evenEmpty Whether to return even if cell has no properties
3022         * @return Whether to return cell property in XMLA result
3023         */
3024        boolean shouldReturnCellProperty(
3025            CellSet cellSet,
3026            Property cellProperty,
3027            boolean evenEmpty);
3028
3029        /**
3030         * Returns a list of names of roles in the given schema to which the
3031         * current user belongs.
3032         *
3033         * @param schema Schema
3034         * @return List of roles
3035         */
3036        List<String> getSchemaRoleNames(Schema schema);
3037
3038        /**
3039         * Returns the unique ID of a schema.
3040         */
3041        String getSchemaId(Schema schema);
3042
3043        String getCubeType(Cube cube);
3044
3045        boolean isLevelUnique(Level level);
3046
3047        /**
3048         * Returns the defined properties of a level. (Not including system
3049         * properties that every level has.)
3050         *
3051         * @param level Level
3052         * @return Defined properties
3053         */
3054        List<Property> getLevelProperties(Level level);
3055
3056        boolean isPropertyInternal(Property property);
3057
3058        /**
3059         * Returns a list of the data sources in this server. One element
3060         * per data source, each element a map whose keys are the XMLA fields
3061         * describing a data source: "DataSourceName", "DataSourceDescription",
3062         * "URL", etc. Unrecognized fields are ignored.
3063         *
3064         * @param connection Connection
3065         * @return List of data source definitions
3066         * @throws OlapException on error
3067         */
3068        List<Map<String, Object>> getDataSources(OlapConnection connection)
3069            throws OlapException;
3070
3071        /**
3072         * Returns a map containing annotations on this element.
3073         *
3074         * @param element Element
3075         * @return Annotation map, never null
3076         */
3077        Map<String, Object> getAnnotationMap(MetadataElement element)
3078            throws SQLException;
3079
3080        /**
3081         * Returns a boolean indicating if the specified
3082         * cell can be drilled on.
3083         */
3084        boolean canDrillThrough(Cell cell);
3085
3086        /**
3087         * Returns the number of rows returned by a
3088         * drillthrough on the specified cell. Will also
3089         * return -1 if it cannot determine the cardinality.
3090         */
3091        int getDrillThroughCount(Cell cell);
3092
3093        /**
3094         * Makes the connection send a command to the server
3095         * to flush all caches.
3096         */
3097        void flushSchemaCache(OlapConnection conn) throws OlapException;
3098
3099        /**
3100         * Returns the key for a given member.
3101         */
3102        Object getMemberKey(Member m) throws OlapException;
3103
3104        /**
3105         * Returns the ordering key for a given member.
3106         */
3107        Object getOrderKey(Member m) throws OlapException;
3108
3109        class FunctionDefinition {
3110            public final String functionName;
3111            public final String description;
3112            public final String parameterList;
3113            public final int returnType;
3114            public final int origin;
3115            public final String interfaceName;
3116            public final String caption;
3117
3118            public FunctionDefinition(
3119                String functionName,
3120                String description,
3121                String parameterList,
3122                int returnType,
3123                int origin,
3124                String interfaceName,
3125                String caption)
3126            {
3127                this.functionName = functionName;
3128                this.description = description;
3129                this.parameterList = parameterList;
3130                this.returnType = returnType;
3131                this.origin = origin;
3132                this.interfaceName = interfaceName;
3133                this.caption = caption;
3134            }
3135        }
3136    }
3137
3138    /**
3139     * Default implementation of {@link mondrian.xmla.XmlaHandler.XmlaExtra}.
3140     * Connections based on mondrian's olap4j driver can do better.
3141     */
3142    private static class XmlaExtraImpl implements XmlaExtra {
3143        public ResultSet executeDrillthrough(
3144            OlapStatement olapStatement,
3145            String mdx,
3146            boolean advanced,
3147            String tabFields,
3148            int[] rowCountSlot) throws SQLException
3149        {
3150            return olapStatement.executeQuery(mdx);
3151        }
3152
3153        public void setPreferList(OlapConnection connection) {
3154            // ignore
3155        }
3156
3157        public Date getSchemaLoadDate(Schema schema) {
3158            return new Date();
3159        }
3160
3161        public int getLevelCardinality(Level level) {
3162            return level.getCardinality();
3163        }
3164
3165        public void getSchemaFunctionList(
3166            List<FunctionDefinition> funDefs,
3167            Schema schema,
3168            Util.Functor1<Boolean, String> functionFilter)
3169        {
3170            // no function definitions
3171        }
3172
3173        public int getHierarchyCardinality(Hierarchy hierarchy) {
3174            int cardinality = 0;
3175            for (Level level : hierarchy.getLevels()) {
3176                cardinality += level.getCardinality();
3177            }
3178            return cardinality;
3179        }
3180
3181        public int getHierarchyStructure(Hierarchy hierarchy) {
3182            return 0;
3183        }
3184
3185        public boolean isHierarchyParentChild(Hierarchy hierarchy) {
3186            return false;
3187        }
3188
3189        public int getMeasureAggregator(Member member) {
3190            return RowsetDefinition.MdschemaMeasuresRowset
3191                .MDMEASURE_AGGR_UNKNOWN;
3192        }
3193
3194        public void checkMemberOrdinal(Member member) {
3195            // nothing to do
3196        }
3197
3198        public boolean shouldReturnCellProperty(
3199            CellSet cellSet, Property cellProperty, boolean evenEmpty)
3200        {
3201            return true;
3202        }
3203
3204        public List<String> getSchemaRoleNames(Schema schema) {
3205            return Collections.emptyList();
3206        }
3207
3208        public String getSchemaId(Schema schema) {
3209            return schema.getName();
3210        }
3211
3212        public String getCubeType(Cube cube) {
3213            return RowsetDefinition.MdschemaCubesRowset.MD_CUBTYPE_CUBE;
3214        }
3215
3216        public boolean isLevelUnique(Level level) {
3217            return false;
3218        }
3219
3220        public List<Property> getLevelProperties(Level level) {
3221            return level.getProperties();
3222        }
3223
3224        public boolean isPropertyInternal(Property property) {
3225            return
3226                property instanceof Property.StandardMemberProperty
3227                && ((Property.StandardMemberProperty) property).isInternal()
3228                || property instanceof Property.StandardCellProperty
3229                && ((Property.StandardCellProperty) property).isInternal();
3230        }
3231
3232        public List<Map<String, Object>> getDataSources(
3233            OlapConnection connection) throws OlapException
3234        {
3235            Database olapDb = connection.getOlapDatabase();
3236            final String modes = createCsv(olapDb.getAuthenticationModes());
3237            final String providerTypes = createCsv(olapDb.getProviderTypes());
3238            return Collections.singletonList(
3239                Olap4jUtil.mapOf(
3240                    "DataSourceName", (Object) olapDb.getName(),
3241                    "DataSourceDescription", olapDb.getDescription(),
3242                    "URL", olapDb.getURL(),
3243                    "DataSourceInfo", olapDb.getDataSourceInfo(),
3244                    "ProviderName", olapDb.getProviderName(),
3245                    "ProviderType", providerTypes,
3246                    "AuthenticationMode", modes));
3247        }
3248
3249        public Map<String, Object> getAnnotationMap(MetadataElement element) {
3250            return Collections.emptyMap();
3251        }
3252
3253        public boolean canDrillThrough(Cell cell) {
3254            return false;
3255        }
3256
3257        public int getDrillThroughCount(Cell cell) {
3258            return -1;
3259        }
3260
3261        public void flushSchemaCache(OlapConnection conn) {
3262            // no op.
3263        }
3264
3265        public Object getMemberKey(Member m) throws OlapException {
3266            return
3267                m.getPropertyValue(
3268                    Property.StandardMemberProperty.MEMBER_KEY);
3269        }
3270
3271        public Object getOrderKey(Member m) throws OlapException {
3272            return m.getOrdinal();
3273        }
3274    }
3275
3276    private static String createCsv(Iterable<? extends Object> iterable) {
3277        StringBuilder sb = new StringBuilder();
3278        boolean first = true;
3279        for (Object o : iterable) {
3280            if (!first) {
3281                sb.append(',');
3282            }
3283            sb.append(o);
3284            first = false;
3285        }
3286        return sb.toString();
3287    }
3288
3289    /**
3290     * Creates an olap4j connection for responding to XMLA requests.
3291     *
3292     * <p>A typical implementation will probably just use a
3293     * {@link javax.sql.DataSource} or a connect string, but it is important
3294     * that the connection is assigned to the correct catalog, schema and role
3295     * consistent with the client's XMLA context.
3296     */
3297    public interface ConnectionFactory {
3298        /**
3299         * Creates a connection.
3300         *
3301         * <p>The implementation passes the properties to the underlying driver.
3302         *
3303         * @param catalog The name of the catalog to use.
3304         * @param schema The name of the schema to use.
3305         * @param roleName The name of the role to use, or NULL.
3306         * @param props Properties to be passed to the underlying native driver.
3307         * @return An OlapConnection object.
3308         * @throws SQLException on error
3309         */
3310        OlapConnection getConnection(
3311            String catalog,
3312            String schema,
3313            String roleName,
3314            Properties props)
3315            throws SQLException;
3316
3317        /**
3318         * Returns a map of property name-value pairs with which to populate
3319         * the response to the DISCOVER_DATASOURCES request.
3320         *
3321         * <p>Properties correspond to the columns of that request:
3322         * ""DataSourceName", et cetera.</p>
3323         *
3324         * <p>Returns null if there is no pre-configured response; in
3325         * which case, the driver will have to connect to get a response.</p>
3326         *
3327         * @return Column names and values for the DISCOVER_DATASOURCES
3328         * response
3329         */
3330        Map<String, Object> getPreConfiguredDiscoverDatasourcesResponse();
3331    }
3332}
3333
3334// End XmlaHandler.java