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